Hibernate在不同版本下表名的解析

问题发现

前不久在将Hibernate从4.3.5升级到5.2.17时遇到一个兼容性问题,系统中部分实体类注解方式为:

@Entity
@Table(name = "user", schema = "user_db", catalog = "")

执行数据库查询时,会报如下错误

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'base_db.user' doesn't exist
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
    at com.mysql.jdbc.Util.getInstance(Util.java:386)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1052)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4096)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4028)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2490)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2651)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2683)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2144)
    at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:2310)

可见sql在生成时,并没有像旧版本一样,生成user_db.user表的访问,而是使用了jdbc链接串中定义的默认数据库base_db.
将数据库名称的定义字段从schema更换为catalog,问题顺利解决。

@Entity
@Table(name = "user", schema = "", catalog = "user_db")

为了弄清楚@Table注解在两个不同版本下如何解析表名,分别查看了两个版本的实现方式。

Hibernate 4.3.5实现方式

首先SingleTableEntityPersister在构造函数中调用table.getQualifiedName获得表解析后的全名。

public SingleTableEntityPersister(
            final PersistentClass persistentClass, 
            final EntityRegionAccessStrategy cacheAccessStrategy,
            final NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy,
            final SessionFactoryImplementor factory,
            final Mapping mapping) throws HibernateException {

        super( persistentClass, cacheAccessStrategy, naturalIdRegionAccessStrategy, factory );

        // CLASS + TABLE

        joinSpan = persistentClass.getJoinClosureSpan()+1;
        qualifiedTableNames = new String[joinSpan];
        isInverseTable = new boolean[joinSpan];
        isNullableTable = new boolean[joinSpan];
        keyColumnNames = new String[joinSpan][];
        final Table table = persistentClass.getRootTable();
        qualifiedTableNames[0] = table.getQualifiedName( 
                factory.getDialect(), 
                factory.getSettings().getDefaultCatalogName(), 
                factory.getSettings().getDefaultSchemaName() 
        );
        //...省略其他代码

Table类中获取表名使用简单直接的catatlog.schema.name方式。

//Table.class
public String getQualifiedName(Dialect dialect, String defaultCatalog, String defaultSchema) {
        if ( subselect != null ) {
            return "( " + subselect + " )";
        }
        String quotedName = getQuotedName( dialect );
        String usedSchema = schema == null ?
                defaultSchema :
                getQuotedSchema( dialect );
        String usedCatalog = catalog == null ?
                defaultCatalog :
                getQuotedCatalog( dialect );
        return qualify( usedCatalog, usedSchema, quotedName );
    }

public static String qualify(String catalog, String schema, String table) {
        StringBuilder qualifiedName = new StringBuilder();
        if ( catalog != null ) {
            qualifiedName.append( catalog ).append( '.' );
        }
        if ( schema != null ) {
            qualifiedName.append( schema ).append( '.' );
        }
        return qualifiedName.append( table ).toString();
    }

Hibernate 5.2.17实现

AbstractEntityPersister通过determineTableName获得表全名

//AbstractEntityPersister.java
protected String determineTableName(Table table, JdbcEnvironment jdbcEnvironment) {
        if ( table.getSubselect() != null ) {
            return "( " + table.getSubselect() + " )";
        }

        return jdbcEnvironment.getQualifiedObjectNameFormatter().format(
                table.getQualifiedTableName(),
                jdbcEnvironment.getDialect()
        );
    }

其中JdbcEnvironment是如何决定用哪个QualifiedObjectNameFormatter?

//Hibernate源码中的JdbcEnvironmentImpl
private NameQualifierSupport determineNameQualifierSupport(DatabaseMetaData databaseMetaData) throws SQLException {
        final boolean supportsCatalogs = databaseMetaData.supportsCatalogsInTableDefinitions();
        final boolean supportsSchemas = databaseMetaData.supportsSchemasInTableDefinitions();

        if ( supportsCatalogs && supportsSchemas ) {
            return NameQualifierSupport.BOTH;
        }
        else if ( supportsCatalogs ) {
            return NameQualifierSupport.CATALOG;
        }
        else if ( supportsSchemas ) {
            return NameQualifierSupport.SCHEMA;
        }
        else {
            return NameQualifierSupport.NONE;
        }
    }

//mysql-connector中JDBC4DatabaseMetaData
public boolean supportsSchemasInTableDefinitions() throws SQLException {
    return false;
}

可见,由于mysql-connector中已经定义了不支持schema的定义,所以使用的是CATALOG类型的CatalogNameFormat。因此sql中表名称的格式为:catalog.name

private static class CatalogNameFormat implements Format {
        private final String catalogSeparator;

        public CatalogNameFormat(String catalogSeparator) {
            this.catalogSeparator = catalogSeparator;
        }

        @Override
        public String format(Identifier catalog, Identifier schema, Identifier name, Dialect dialect) {
            StringBuilder buff = new StringBuilder();

            if ( catalog != null ) {
                buff.append( render( catalog, dialect ) ).append( catalogSeparator );
            }

            buff.append( render( name, dialect ) );

            return buff.toString();
        }
    }

小结

  1. 在Hibernate 4.3.5中,不管用catalog还是schema都可以正确拼接出表名,但是不能两个字段同时使用。
  2. 在Hibernate 5.2.17中,schema属性可以完全忽略。
  3. 在mysql中schema和database可以理解为相同的概念,所以mysql-connector实现也不再支持schema了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

22986547

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值