JPA: Как указать имя таблицы, соответствующее классу во время выполнения?

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

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

По-видимому, это не так просто сделать, и я был бы признателен за подсказку.

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

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

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

Ответ 1

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

Или может быть что-то вроде Dynamic JPA вас заинтересовало бы?

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

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

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

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

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

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

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

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

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

Ответ 2

Вы можете указать имя таблицы во время загрузки через пользовательский 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);
    }

}

Ответ 3

Мне кажется, что вы после этого Переопределение аннотаций 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>

Ответ 4

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

Ответ 5

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