如何在运行时使用JPA检索实体的映射表名称?

39

能否确定实体的本地表名?

如果存在Table注释,这很容易:

entityClass.getAnnotation(Table.class).name()

如果没有Table注解,那怎么办?

Hibernate 通过 Configuration 类提供这些信息:

configuration.getClassMapping(entityClass.getSimpleName()).getTable().getName()

JPA中有类似的东西吗?


1
据我所知,这确实不是标准API的一部分,因此您将不得不依赖于实际的实现(如Hibernate、TopLink等)来获得您想要的内容。 - Stefan De Boey
6个回答

24
这是我使用EclipseLink的方法(没有映射文件):
/**
 * Returns the table name for a given entity type in the {@link EntityManager}.
 * @param em
 * @param entityClass
 * @return
 */
public static <T> String getTableName(EntityManager em, Class<T> entityClass) {
    /*
     * Check if the specified class is present in the metamodel.
     * Throws IllegalArgumentException if not.
     */
    Metamodel meta = em.getMetamodel();
    EntityType<T> entityType = meta.entity(entityClass);

    //Check whether @Table annotation is present on the class.
    Table t = entityClass.getAnnotation(Table.class);

    String tableName = (t == null)
                        ? entityType.getName().toUpperCase()
                        : t.name();
    return tableName;
}

2
不要认为这是正确的方式,例如对于名称为SomeComplexName的实体,该方法会返回什么? - skwisgaar
1
据我所知,默认行为是将驼峰命名实体映射到蛇形表格(例如,SomeComplexName -> some_complex_name)。不过我可能错了 :) - skwisgaar
1
是的,对我来说也一样(意思是,当我运行代码示例时)。但我的表格名称是蛇形命名的,所以我最终将其按大写字母拆分并用下划线连接起来:String tableName = String.join("_", entity.getClass().getSimpleName().split("(?=\\p{Upper})"));。这远非完美,但对于我相当简单的情况足够了(我在测试中使用它)。 - skwisgaar
1
如果@Table没有指定表名,并且使用了一些可配置的命名策略来将实体名称转换为数据库表名,则此方法无法正常工作。 - cdalxndr
如果您正在使用Spring Boot,请注意它使用自己的命名策略(覆盖了Hibernate的策略)。结果是,如果未指定注释,则默认表名为类名转换为“snake_case”。实质上,Spring Boot在这方面并不完全符合JPA规范...但是引导您使用snake_case...我认为这是可以接受的覆盖,即使不是JPA规范所说的。 - peterh
显示剩余2条评论

4
我的同事在使用Hibernate作为后端的Spring Data JPA环境中找到了以下解决方案:
import org.hibernate.internal.SessionImpl;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;

@Service
public class EntityClassToTableNameMapper {

    @Transactional
    public String[] getTableNames(EntityManager em, Class entityClass) {

        Object entityExample;
        try {
            entityExample = entityClass.newInstance();
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }

        SessionImpl session = em.unwrap(SessionImpl.class);

        EntityPersister persister = session.getEntityPersister(null, entityExample);

        if (persister instanceof AbstractEntityPersister) {
            AbstractEntityPersister persisterImpl = (AbstractEntityPersister) persister;

            String tableName = persisterImpl.getTableName();

            String rootTableName = persisterImpl.getRootTableName();

            return new String[] {rootTableName, tableName};

        } else {
            throw new RuntimeException("Unexpected persister type; a subtype of AbstractEntityPersister expected.");
        }
    }
}

3

3
我想避免再次实现算法。我也想避免解析XML映射文件。但我已经想到,没有办法询问JPA实现真正的表名。非常感谢。 - marabol
您可以使用 orm.xml 文件覆盖名称,因此在编程重新执行算法时也需要读取正确的 orm 文件。 - Καrτhικ

1
如果您使用@Table注释,就像您所展示的那样,就没有问题。如果您不使用该注释,则表名与类名相同(JPA默认)。
如果您使用映射文件,那么乐趣开始了,您需要解析它并检索表名 - 这并不是非常困难,但需要一些工作。如果您担心性能问题,那么可以解析映射文件一次并缓存所有表名。

0

询问底层ORM的元模型是最可靠的方法:仅查看@Table的存在是不够的,因为它可能会被XML配置(例如orm.xml)覆盖,并且在使用JOINED策略时,@Table可能位于超类上。


1
这只是一个注释。 - jogo

0
对于Spring Data JPA 3.1.2,这个工作很好。
Metamodel metamodel = entityManager.getMetamodel();
MappingMetamodel mappingMetamodel = ((MappingMetamodelImpl) metamodel).getMappingMetamodel();
EntityPersister entityPersister = ((MappingMetamodelImpl) mappingMetamodel).entityPersister(User.class.name); // User is entity class
SingleTableEntityPersister entityPersisterImpl = (SingleTableEntityPersister) entityPersister.getEntityPersister();

System.out.println(entityPersisterImpl.getTableName());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接