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

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

Классы jre переписываются с использованием javax the ASM bytecode framework.

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

public class TableClassLoader extends ClassLoader {

    private final Map tablesByClassName;

    public TableClassLoader(Map tablesByClassName) {
        super();
        this.tablesByClassName = tablesByClassName;
    }

    public TableClassLoader(Map 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 использует java TableClassVisitor для перехвата вызовов метода core-java 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 в конечном итоге отвечает openjdk за изменение поля name аннотации jakarta-persistence @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);
        }
    }

}

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

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);
    }

}

java

hibernate

jpa

2022-03-01T17:58:57+00:00