Использование столбца MySQL SET с Ebean ORM

Я пытаюсь воссоздать одно из наших веб-приложений, используя Play 2.0 с Ebean, и столкнулся с препятствием. Я не могу понять, как сопоставить тип SET MySQL с полем в модели. Я получил столбцы ENUM, работающие с аннотацией @Enumerated(EnumType.STRING), но я не могу найти никакой информации о столбцах SET.

Таблица имитирует crontab:

CREATE TABLE IF NOT EXISTS `schedule` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `task_id` mediumint(8) unsigned NOT NULL default '0',
  `month` set('January','February','March','April','May','June','July','August','September','October','November','December') default NULL,
  `mday` set('1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','-1','-2','-3','-4','-5','-6','-7','-8','-9','-10') default NULL,
  `wday` set('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday') default NULL,
  `hour` set('0','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','23') default NULL,
  `minute` set('00','01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50','51','52','53','54','55','56','57','58','59') default NULL,
  `updated` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
  PRIMARY KEY  (`id`),
  KEY `event` (`task_id`)
)

Теперь я создал UserType и связанные с ним аннотации, как это было предложено MvG:

@Entity
public class Schedule extends Model {

    public enum Month { JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER };
    public enum Weekday { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY };

    @Id
    public Long id;

    @ManyToOne(cascade = CascadeType.MERGE)
    public Task task;
    @Version
    public Timestamp updated;

    @Type(type="models.EnumSetUserType",parameters=@Parameter(name="enumType",value="models.Schedule$Month"))
    @Column(name="month", columnDefinition="SET('JANUARY','FEBRUARY','MARCH','APRIL','MAY','JUNE','JULY','AUGUST','SEPTEMBER','OCTOBER','NOVEMBER','DECEMBER')")
    @MonthEnum
    public EnumSet<Month> months;

    @Type(type="models.IntegerSetUserType")
    @IntegerSet(min=-30,max=30)
    @Column(name="mday",columnDefinition="SET('1','2','3','4','5','6','7','9','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','-1','-2','-3','-4','-5','-6','-7','-8','-9')")
    public Set<Integer> mdays;

    @Type(type="models.EnumSetUserType", parameters = @Parameter(name="enumType", value="models.Schedule$Weekday"))
    @Column(name="wday", columnDefinition="SET('MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY','SATURDAY')")
    @WeekdayEnum
    public EnumSet<Weekday> weekdays;

    @Type(type="models.IntegerSetUserType")
    @IntegerSet(min=0,max=23)
    @Column(name="hour",columnDefinition="SET('0','1','2','3','4','5','6','7','9','10','11','12','13','14','15','16','17','18','19','20','21','22','23')")
    public Set<Integer> hours;

    @Type(type="models.IntegerSetUserType")
    @IntegerSet(min=0,max=59)
    @Column(name="minute",columnDefinition="SET('00','01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50','51','52','53','54','55','56','57','58','59')")
    public Set<Integer> minutes;

    public static Finder<Long,Schedule> find = new Finder<Long,Schedule>(Long.class, Schedule.class);

}

EnumSetUserType:

public class EnumSetUserType<E extends Enum<E>> implements UserType, ParameterizedType, Serializable {
    private Class<? extends EnumSet> clazz = null;
    private Class<E> enum_type = null;

    @Override
    public void setParameterValues(Properties parameters) {
        String enum_class_name = parameters.getProperty("enumType");
        try {
            enum_type = ReflectHelper.classForName(parameters.getProperty("enumType"), this.getClass()).asSubclass(Enum.class);
            //enum_type = (Class<E>) Class.forName(enum_class_name);
            //enum_type = (Class<E>) Play.application().classloader().loadClass(enum_class_name);
        }
        catch (ClassNotFoundException e) {
            throw new HibernateException("enum class " + enum_class_name + " not found", e);
        }
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] column_names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
        String value_str = rs.getString(column_names[0]);
        System.out.println("getting " + column_names[0] + " using " + getClass());
        if (rs.wasNull())
            return null;

        List<E> enum_values = new ArrayList<E>();
        for (String value : value_str.split(","))
            enum_values.add(Enum.valueOf(enum_type, value));

        return EnumSet.copyOf(enum_values);
    }

    @Override
    public void nullSafeSet(PreparedStatement statement, Object object, int index, SessionImplementor session) throws HibernateException, SQLException {
        System.out.println("Setting " + index + " to " + object + " using " + getClass());
        if (object == null) {
            statement.setNull(index, Types.VARCHAR);
            return;
        }

        Set<E> values = (Set<E>) object;
        StringBuilder sb = new StringBuilder();
        for (E value : values)
            sb.append(value.name()).append(",");

        System.out.println("Setting " + index + " to " + sb.length() + " using " + getClass());
        statement.setString(index, sb.substring(0, sb.length() - 1));
    }

    @Override
    public int[] sqlTypes() {
        return new int[] { Types.VARCHAR };
    }

    @Override
    public Class returnedClass() {
        return clazz;
    }

    @Override
    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

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

    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }

    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return cached;
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == y)
            return true;

        if (x == null || y == null)
            return false;

        return x.equals(y);
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }

}

Он больше не сообщает об ошибках, но даже когда я вручную заполняю и сохраняю Schedule в действии контроллера, поля Set не сохраняются в базе данных. Как заставить Play+Ebean работать с этой таблицей?


person Brad Mace    schedule 28.06.2012    source источник
comment
Это помогает? stackoverflow.com/questions/1126029/   -  person Salil    schedule 29.06.2012
comment
Я... не могу сказать. Трудно ориентироваться, когда все называется Wicket, и я не уверен, как конфигурация XML будет отображаться в аннотациях.   -  person Brad Mace    schedule 29.06.2012


Ответы (1)


Если вы сопоставляете столбец ENUM с перечислением Java, вам, вероятно, следует сопоставлять столбец SET с Java EnumSet. В SF есть по крайней мере один вопрос о сопоставлении EnumSet, но решение там выглядит как отдельная таблица, а не mysql SET тип.

Также кажется, что в hybernate нет поддержки для типов mysql SET. Поэтому вам придется написать собственный UserType. Я не уверен в разнице, но кажется, что вы могли бы легко сделать это EnhancedUserType, что, вероятно, сделало бы вашу реализацию более универсальной. Если ваш проект позволяет использовать исходный код под лицензией LGPL, вы можете использовать реализация EnumType в качестве шаблона для вашего собственного EnumSetType. Адаптировать это должно быть легко, особенно потому, что вы можете выбросить все части кода, которые «сохраняют как порядковые».

Если у вас есть собственный UserType для EnumSet, вы можете аннотировать соответствующие поля этим @Type. Или в конфигурации гибернации, если в вашей настройке есть такая штука. Могут быть даже способы зарегистрировать тип где-нибудь, чтобы автоматически отображать все экземпляры EnumSet, использующие этот тип, но я слишком мало знаю обо всей этой спячке, чтобы решить, желательно ли это или даже возможно. Я еще даже не понял, как @Enumerated аннотации сопоставляются с реализацией EnumType.

С правильными ключевыми словами (UserType EnumSet split) можно найти некоторые реализации на паутина. Таким образом, вам даже не нужно было бы писать собственный код, а можно было бы просто включить одно из этих решений. Некоторые поставляются с кратким описанием того, как их использовать.

person MvG    schedule 02.07.2012
comment
использование EnumSet приводит к org.hibernate.AnnotationException: незаконная попытка сопоставить не коллекцию как @OneToMany, @ManyToMany или @CollectionOfElements: models.Schedule.months. Или, если я удалю @ElementCollection, он просто притворится, что его не существует. - person Brad Mace; 03.07.2012
comment
@bemace: Хорошо, я отредактировал свой ответ, чтобы включить более подробную информацию о сопоставлениях пользовательских типов. - person MvG; 03.07.2012