JPA: How do I specify the table name corresponding to a class at runtime?

前端 未结 5 2277
执笔经年
执笔经年 2020-12-01 05:11

(note: I\'m quite familiar with Java, but not with Hibernate or JPA - yet :) )

I want to write an application which talks to a DB2/400 database through JPA and I ha

5条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2020-12-01 05:39

    You may be able to specify the table name at load time via a custom ClassLoader that re-writes the @Table annotation on classes as they are loaded. At the moment, I am not 100% sure how you would ensure Hibernate is loading its classes via this ClassLoader.

    Classes are re-written using the ASM bytecode framework.

    Warning: These classes are experimental.

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

    The TableClassLoader relies on the TableClassVisitor to catch the visitAnnotation method calls:

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

    The TableAnnotationVisitor is ultimately responsible for changing the name field of the @Table annotation:

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

    Because I didn't happen to find an AnnotationAdapter class in ASM's library, here is one I made myself:

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

提交回复
热议问题