В MySQL недопустимые значения величин DATETIME, DATE или TIMESTAMP преобразуются в значение «ноль» соответствующего типа величин ('0000-00-00 00:00:00', '0000-00-00', или 00000000000000). Эти же значения могут подставляться в поля как значения по-умолчанию. При попытке выборки такого поля (в момент когда в нем установлена нулевая дата) Вы получите исключение:
SqlException: Value '0000-00-00 00:00:00' can not be represented as java.sql.Timestamp.
Отчасти решить эту проблему можно с помощью настроек Hibernate. Если установить свойство hibernate.connection.zeroDateTimeBehavior
в значение convertToNull
, то нулевые значения дат будут просто преобразовываться в null
не вызывая исключений.
hibernate.cfg.xml:
convertToNull
программная настройка:
configuration .setProperty( "hibernate.connection.zeroDateTimeBehavior", "convertToNull" );
Но при сохранении объекта в БД обратное преобразование из null
не выполняется и если поле не может быть NULL
, то тут же возникнет исключение:
SqlException: Column ‘COLUMN_NAME’ cannot be null
Можно конечно инициализировать поля в классе значениями вроде new Date(0)
. Но это значение, как известно, равно 1 января 1970 года и представляет собой реальную дату, что не совсем соответствует ожиданиям.
Наиболее оптимальным вариантом для работы с датой MYSQL является использование пользовательского типа данных, который позволяет контролировать процессы получения данных из БД и их записи. С помощью методов nullSafeGet
и nullSafeSet
можно преобразовать нулевую дату в null
при выборке и обратно в нулевую дату при записи данных.
import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.util.Date; import org.hibernate.HibernateException; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.usertype.UserType; public class MySQLZeroDate implements UserType { private static final int[] SQL_TYPES = { Types.TIMESTAMP }; /** * Какие SQL типы данных могут отображаться этим классом */ public int[] sqlTypes() { return SQL_TYPES; } /** * Какой тип данных используется для хранения даты в объектах */ @SuppressWarnings("rawtypes") public Class returnedClass() { return Date.class; } /** * Сравнение двух объектов заданного типа данных */ public boolean equals(Object x, Object y) throws HibernateException { if (x == y) { return true; } else if (x == null || y == null) { return false; } else { return x.equals(y); } } /** * Получить hashCode объекта */ public int hashCode(Object arg0) throws HibernateException { if (arg0 != null) { return arg0.hashCode(); } else { return 0; } } /** * Создание глубокой копии объекта */ public Object deepCopy(Object value) throws HibernateException { if (value != null) { return new Date(((Date) value).getTime()); } return value; } /** * Объект нашего типа не изменяемый */ public boolean isMutable() { return false; } /** * Трансформация объекта для помещения в cache */ public Serializable disassemble(Object value) throws HibernateException { return (Serializable) value; } /** * Восстанавление объекта из сериализованного представления */ public Object assemble(Serializable cached, Object owner) throws HibernateException { return cached; } /** * Вызывается во время слияния двух сущностей */ public Object replace(Object original, Object target, Object owner) throws HibernateException { return original; } /** * Создание экземпляра класса из набора сопоставленных данных JDBC */ public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException { // Если дата равна "0000-00-00 00:00:00" возвращаем null Date result = null; if (rs.getString(names[0]) != null && !rs.getString(names[0]).equals("0000-00-00 00:00:00")) { result = new Date(rs.getTimestamp(names[0]).getTime()); } return result; } /** * Запись экземпляра класса соспоставленного типа данных в SQL запрос */ public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException { // Если дата равна null сохраняем "0000-00-00 00:00:00" if (value == null) { st.setString(index, "0000-00-00 00:00:00"); } else { st.setTimestamp(index, new Timestamp(((Date) value).getTime())); } } }
Пример назначения пользовательского типа данных полю класса с помощью аннотаций:
@Type(type = "hibernate.db.MySQLZeroDate") private Date date_order;