JPA: Как мне указать имя таблицы, соответствующее классу во время выполнения? - PullRequest
13 голосов
/ 25 мая 2009

(примечание: я хорошо знаком с Java, но не с Hibernate или JPA - пока :))

Я хочу написать приложение, которое взаимодействует с базой данных DB2 / 400 через JPA, и теперь я могу получить все записи в таблице и перечислить их в System.out (использовал MyEclipse для обратного инжиниринга). Я понимаю, что аннотация @Table приводит к тому, что имя статически компилируется с классом, но мне нужно иметь возможность работать с таблицей, в которой имя и схема предоставляются во время выполнения (их определения совпадают, но у нас есть много их).

Очевидно, это не так легко сделать, и я был бы признателен за подсказку.

В настоящее время я выбрал Hibernate в качестве поставщика JPA, поскольку он может справиться с тем, что эти таблицы базы данных не регистрируются.

Итак, вопрос в том, как во время выполнения сказать Hibernate-реализации JPA, что класс A соответствует таблице базы данных B?

(редактировать: переопределенная tableName () в Hibernate NamingStrategy может позволить мне обойти это внутреннее ограничение, но я все же предпочел бы решение JPA, независимое от поставщика)

Ответы [ 5 ]

15 голосов
/ 25 мая 2009

Вам необходимо использовать XML-версию конфигурации, а не аннотации. Таким образом, вы можете динамически генерировать XML во время выполнения.

А может, что-то вроде Динамический JPA вас заинтересует?

Я думаю, что необходимо уточнить проблемы с этой проблемой.

Первый вопрос: известен ли набор таблиц, в которых можно хранить сущность? Под этим я подразумеваю, что вы не создаете таблицы динамически во время выполнения и не хотите связывать с ними сущности. Этот сценарий требует, чтобы, скажем, три таблицы были известны в время компиляции . Если это так, вы можете использовать наследование JPA. Документация OpenJPA детализирует таблицу для класса стратегия наследования.

Преимущество этого метода в том, что это чистый JPA. Однако он имеет ограничения, заключающиеся в том, что таблицы должны быть известны, и вы не можете легко изменить, в какой таблице хранится данный объект (если это требуется для вас), так же как объекты в ОО-системах обычно не изменяют класс или введите.

Если вы хотите, чтобы это было действительно динамично и для перемещения сущностей между таблицами (по сути), то я не уверен, что JPA - подходящий инструмент для вас. Огромное количество магии идет на работу JPA, включая ткачество во время загрузки (инструментарий) и, как правило, один или несколько уровней кэширования. Более того, менеджеру сущностей необходимо регистрировать изменения и обрабатывать обновления управляемых объектов. Я знаю, что нет простого способа проинструктировать менеджера сущностей о том, что данная сущность должна храниться в той или иной таблице.

Такая операция перемещения неявно потребует удаления из одной таблицы и вставки в другую. Если есть дочерние объекты, это становится сложнее. Не исключено, но это такой необычный угловой случай, я не уверен, что кто-нибудь когда-либо будет беспокоиться.

Низкоуровневая структура SQL / JDBC, такая как Ibatis , может быть лучше, поскольку она даст вам необходимый контроль.

Я также подумал о динамическом изменении или назначении аннотаций во время выполнения. Хотя я еще не уверен, возможно ли это, даже если это так, я не уверен, что это обязательно поможет. Я не могу себе представить, чтобы менеджер сущностей или кеширование не были безнадежно запутаны подобными вещами.

Другой возможностью, о которой я подумал, было динамическое создание подклассов во время выполнения (как анонимных подклассов), но это все еще имеет проблему с аннотациями, и опять же я не уверен, как вы добавите это в существующий модуль персистентности.

Это может помочь, если вы предоставите более подробную информацию о том, что вы делаете и почему. Что бы это ни было, я склоняюсь к мысли, что вам нужно переосмыслить то, что вы делаете или как вы это делаете, или вам нужно выбрать другую технологию персистентности.

9 голосов
/ 02 июня 2009

Вы можете указать имя таблицы во время загрузки с помощью пользовательского ClassLoader , который перезаписывает аннотацию @Table для классов по мере их загрузки. На данный момент я не уверен на 100%, как вы могли бы гарантировать, что Hibernate загружает свои классы через этот ClassLoader.

Классы переписываются с использованием инфраструктуры байт-кода ASM .

Предупреждение: Эти классы являются экспериментальными.

public class TableClassLoader extends ClassLoader {

    private final Map<String, String> tablesByClassName;

    public TableClassLoader(Map<String, String> tablesByClassName) {
        super();
        this.tablesByClassName = tablesByClassName;
    }

    public TableClassLoader(Map<String, String> tablesByClassName, ClassLoader parent) {
        super(parent);
        this.tablesByClassName = tablesByClassName;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (tablesByClassName.containsKey(name)) {
            String table = tablesByClassName.get(name);
            return loadCustomizedClass(name, table);
        } else {
            return super.loadClass(name);
        }
    }

    public Class<?> loadCustomizedClass(String className, String table) throws ClassNotFoundException {
        try {
            String resourceName = getResourceName(className);
            InputStream inputStream = super.getResourceAsStream(resourceName);
            ClassReader classReader = new ClassReader(inputStream);
            ClassWriter classWriter = new ClassWriter(0);
            classReader.accept(new TableClassVisitor(classWriter, table), 0);

            byte[] classByteArray = classWriter.toByteArray();

            return super.defineClass(className, classByteArray, 0, classByteArray.length);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String getResourceName(String className) {
        Type type = Type.getObjectType(className);
        String internalName = type.getInternalName();
        return internalName.replaceAll("\\.", "/") + ".class";
    }

}

TableClassLoader использует TableClassVisitor для перехвата вызовов метода visitAnnotation :

public class TableClassVisitor extends ClassAdapter {

    private static final String tableDesc = Type.getDescriptor(Table.class);

    private final String table;

    public TableClassVisitor(ClassVisitor visitor, String table) {
        super(visitor);
        this.table = table;
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        AnnotationVisitor annotationVisitor;

        if (desc.equals(tableDesc)) {
            annotationVisitor = new TableAnnotationVisitor(super.visitAnnotation(desc, visible), table);
        } else {
            annotationVisitor = super.visitAnnotation(desc, visible);
        }

        return annotationVisitor;
    }

}

TableAnnotationVisitor в конечном итоге отвечает за изменение поля name аннотации @Table:

public class TableAnnotationVisitor extends AnnotationAdapter {

    public final String table;

    public TableAnnotationVisitor(AnnotationVisitor visitor, String table) {
        super(visitor);
        this.table = table;
    }

    @Override
    public void visit(String name, Object value) {
        if (name.equals("name")) {
            super.visit(name, table);
        } else {
            super.visit(name, value);
        }
    }

}

Поскольку мне не удалось найти класс AnnotationAdapter в библиотеке ASM, вот один, который я сделал сам:

public class AnnotationAdapter implements AnnotationVisitor {

    private final AnnotationVisitor visitor;

    public AnnotationAdapter(AnnotationVisitor visitor) {
        this.visitor = visitor;
    }

    @Override
    public void visit(String name, Object value) {
        visitor.visit(name, value);
    }

    @Override
    public AnnotationVisitor visitAnnotation(String name, String desc) {
        return visitor.visitAnnotation(name, desc);
    }

    @Override
    public AnnotationVisitor visitArray(String name) {
        return visitor.visitArray(name);
    }

    @Override
    public void visitEnd() {
        visitor.visitEnd();
    }

    @Override
    public void visitEnum(String name, String desc, String value) {
        visitor.visitEnum(name, desc, value);
    }

}
4 голосов
/ 19 октября 2010

Мне кажется, что вы ищете Переопределение аннотаций JPA с помощью ORM.xml .

Это позволит вам указать аннотации, но затем переопределять их только там, где они изменяются. Я сделал то же самое, чтобы переопределить schema в аннотации @Table, когда она меняется между моими средами.

Используя этот подход, вы также можете переопределить имя таблицы для отдельных объектов.

[Обновление этого ответа, поскольку оно не очень хорошо задокументировано, и кто-то еще может найти его полезным]

Вот мой файл orm.xml (обратите внимание, что я только переопределяю схему и оставляю другие аннотации JPA и Hibernate в одиночку, однако изменение таблицы здесь вполне возможно. Также обратите внимание, что я комментирую на поле не добытчик)

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings 
  xmlns="http://java.sun.com/xml/ns/persistence/orm"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd"
  version="1.0">
    <package>models.jpa.eglobal</package>
    <entity class="MyEntityOne" access="FIELD">
        <table name="ENTITY_ONE" schema="MY_SCHEMA"/>
    </entity> 
    <entity class="MyEntityTwo" access="FIELD">
        <table name="ENTITY_TWO" schema="MY_SCHEMA"/>
    </entity> 
</entity-mappings>
3 голосов
/ 25 мая 2009

в качестве альтернативы конфигурации XML вы можете динамически генерировать Java-класс с аннотацией, используя предпочитаемую среду манипулирования байт-кодом

1 голос
/ 28 мая 2009

Если вы не против привязать себя к Hibernate, вы можете использовать некоторые методы, описанные в https://www.hibernate.org/171.html. Вы можете найти себя, используя довольно много спящих аннотаций в зависимости от сложности ваших данных, так как они выходят за пределы спецификации JPA, так что это может быть небольшой ценой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...