Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java EE (J2EE) и Spring > Hibernate+Spring автогенерация филда


Автор: CPlusPlusFAN 31.5.2015, 14:33
Приветствую!

Возникла такая проблема. Вот есть энтити:
Код

@Entity
@Table(name = "test")
public class TestEntity {
    @Id
    @GeneratedValue(generator = "uuid_gen")
    @GenericGenerator(name = "uuid_gen", strategy = "ru.test.hiber.entities.UUIDGenerator")
    @Column(name = "id", length = 32, nullable = false, unique = true)
    private String id;
    @Column(name = "data", length = 32, nullable = false)
    private String data;

    // Get & Set
}

public class UUIDGenerator implements IdentifierGenerator {
    @Override
    public Serializable generate(SessionImplementor session, Object obj) {
        UUID uuid = UUID.randomUUID();
        String result = uuid.toString().replaceAll("-", "");
        return result;
    }
}


В его поле data нужно записать значение ключа id этой же энтити. Иными словами, для каждой записи в таблице test должны совпадать значения полей id и data.
У него data NOT NULL, поэтому хорошо бы обойтись одним персистом.
Можно ли в Spring+Hibernate при выполнении персиста перед непосредственно самим персистом, но после генерации ключа скопировать этот ключ?
@EntityListener пробовал - не заработал.

Премного благодарен!

Автор: AntonSaburov 1.6.2015, 10:35
Может глянуть @PrePersist ?

Автор: CPlusPlusFAN 1.6.2015, 10:53
Код

@Entity
@Table(name = "test")
@EntityListeners(PrePersistListener.class)
public class TestEntity {
// Missing
}

class PrePersistListener {
    @PrePersist
    public void prePersist(TestEntity testEntity )
    {
        // pre persist
    }
}

У меня не работало. Может там прототип метода должен быть другим?

Автор: AntonSaburov 2.6.2015, 10:05
Т.е. делаешь save и листенер не "слышит" ? Это может быть проблема реализации JPA. Какой провайдер для этого используется ?

Автор: CPlusPlusFAN 2.6.2015, 10:50
Хибер 3 и 4. А может это работает только для энтити менеджера? У меня сессион фактори.

Автор: AntonSaburov 2.6.2015, 16:31
Это функциональность JPA, так что по идее надо работать через EntityManager. Нужно попробовать.

Автор: CPlusPlusFAN 2.6.2015, 20:55
AntonSaburov, именно! Если юзать энтити менеджер, то события легко перехватываются.
Эх, генерация ключа происходит после перехвата события.  smile 

ПС В общем, сделал свою реализацию перехвата генерации ключа:
Код

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PostGenerate {
}

public class UUIDGenerator implements IdentifierGenerator, Configurable {
    private Object postGenerateInstance; // Инстанс листенера
    private List<Method> postGenerateMethods = new ArrayList<Method>(); // Колбек методы
    private boolean insideEntity; // Листенер - сам энтити

    static private final Class<?> KEY_CLASS = String.class;

    @Override
    public Serializable generate(SessionImplementor session, Object obj) {
        UUID uuid = UUID.randomUUID();
        String result = uuid.toString().replaceAll("-", "");

        if (insideEntity) {
            postGenerateInstance = obj;
        }
        // Вызов колбеков
        if (postGenerateInstance != null && postGenerateMethods != null) {
            for (Method method : postGenerateMethods) {
                try {
                    method.setAccessible(true);
                    if (insideEntity) {
                        method.invoke(postGenerateInstance, new Object[] { result });
                    } else {
                        method.invoke(postGenerateInstance, new Object[] { obj, result });
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }

        return result;
    }

    @Override
    public void configure(Type type, Properties params, Dialect d) throws MappingException {
        postGenerateInstance = null;
        postGenerateMethods.clear();

        try {
            Class<?> entityClass = Class.forName(params.get("entity_name").toString()); // Класс энтити, задан в пропертях
            Class<?> postGenerateClass = entityClass; // По дефолту листенер - сам энтити

            Object postGenerateValue = params.get("post-generate"); // Получаем класс листенера из параметра
            if (postGenerateValue != null) {
                String postGenerateClassName = postGenerateValue.toString();
                postGenerateClass = Class.forName(postGenerateClassName);
            }
            
            insideEntity = (entityClass == postGenerateClass);
            if (!insideEntity) { // Листенер должен иметь конструктор без параметров
                postGenerateInstance = postGenerateClass.newInstance();
            }
            // Ищем колбеки
            for (Method method : postGenerateClass.getDeclaredMethods()) {
                method.setAccessible(true);
                if (isMethodSuitable(postGenerateClass, entityClass, method)) {
                    postGenerateMethods.add(method);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private boolean isMethodSuitable(Class<?> listenerClass, Class<?> entityClass,
            Method listenerMethod) {
        if (!listenerMethod.isAnnotationPresent(PostGenerate.class)) { // Выбираем аннотированные методы
            return false;
        }

        Class<?>[] parameterTypes = listenerMethod.getParameterTypes();
        // У метода должны быть либо 2 параметра: энтити и ключ (для случая отдельного класса колбека),
        // либо 1 - ключ (для случая колбека внутри энтити)
        return (insideEntity && parameterTypes.length == 1
                && parameterTypes[0].isAssignableFrom(KEY_CLASS) || !insideEntity
                && parameterTypes.length == 2 && parameterTypes[0].isAssignableFrom(entityClass)
                && parameterTypes[1].isAssignableFrom(KEY_CLASS));
    }
}

// 1 Колбек внутри энтити
@Entity
@Table(name = "test")
public class TestEntity {
    @Id
    @GeneratedValue(generator = "uuid_gen")
    @GenericGenerator(name = "uuid_gen", strategy = "ru.test.hiber.entities.UUIDGenerator", parameters = {
            @Parameter(name = "post-generate", value = "ru.test.hiber.entities.TestEntity") // Опционально
    })
    @Column(name = "id", length = 32, nullable = false, unique = true)
    private String id;
    @Column(name = "data", length = 100, nullable = false)
    private String data;

    // Get & Set
    
    @PostGenerate
    public void generatedKey(String key) {
        // Ключ сгенерен, но не записан в энтити
    }
}

// 2 Колбек в отдельном классе
@Entity
@Table(name = "test")
public class TestEntity {
    @Id
    @GeneratedValue(generator = "uuid_gen")
    @GenericGenerator(name = "uuid_gen", strategy = "ru.test.hiber.entities.UUIDGenerator", parameters = {
            @Parameter(name = "post-generate", value = "ru.test.hiber.entities.TestEntityListener")
    })
    @Column(name = "id", length = 32, nullable = false, unique = true)
    private String id;
    @Column(name = "data", length = 100, nullable = false)
    private String data;

    // Get & Set
}

public class TestEntityListener {
    @PostGenerate
    public void postGen(TestEntity entity, String key) {
        // Ключ сгенерен, но не записан в энтити
    }
}

Автор: AntonSaburov 3.6.2015, 11:08
Было у меня такое подозрение, что ключ к этому времени может и не появится. Ну может тогда проще поставить триггер на базу ? Или вызывать генератор самому при создании объекта прямо в конструкторе.

Автор: CPlusPlusFAN 3.6.2015, 12:59
AntonSaburov, я делаю так: когда дергается генератор, то ключ уже точно будет сгенерен. При этом, в генераторе есть вся необходимая информация об энтити, в которую нужно записать ключ.
Более того, генераторы поддерживают конфигурирование параметрами, в которые можно передать класс листенера.
ПС Ну, надеюсь, мой снипет хоть кому-то пригодится, кто вдруг столкнется с аналогичной проблемой. 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)