休眠:与两个@JoinColumn的关系具有对应的MappingException,并为其列之一附加了PK只读映射

时间:2019-01-03 23:46:16

标签: hibernate jpa mapping

请注意以下数据库设计:

DB design

注意:不必介意此“设计”的创建者有什么想法,但我无法更改。

这与MobileTansAuthenticators之间的关系...

MobileTans表的各个映射:

@Entity
@Table(name="MobileTans")
public class MobileTan implements Serializable
{
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name="ID", insertable=false, updatable=false)
    private Integer id;    // dupe read-only field, ID already in relationship below (writable)

    @Basic(optional=false)
    @Column(name="MOBILE_PHONE_NBR")
    private String mobilePhoneNbr;

    @Basic
    @Column(name="ACTIVATION_CODE")
    private String activationCode;

    @Basic
    @Column(name="ACTIVATION_ERROR")
    private Boolean activationError = Boolean.FALSE;

    @Basic
    @Column(name="STATUS")
    private Integer status;

    @OneToOne(optional=false, fetch=FetchType.EAGER)
    @JoinColumn(name="ID", referencedColumnName="ID")
    @JoinColumn(name="AUTH_NAME", referencedColumnName="AUTHENTICATOR")
    private Authenticator authenticator;

    ...
}

这里的线索是insertable=false, updatable=false列的重复只读映射(@Id)。

ID列被定义为PK,因此将@Id设置为关系是没有用的。我必须创建一个额外的列映射。我将只读放在额外的列上,因为Hibernate(或JPA)不允许我仅对关系的一部分指定只读(我认为JPA规范禁止这样做)。

编辑:身份验证者实体

@Entity
@Table(name="Authenticators")
@IdClass(AuthenticatorPk.class)
public class Authenticator implements Serializable
{
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name="ID")
    private Integer id;

    @Id
    @Column(name="AUTHENTICATOR")
    private String authenticator = "";

    @Basic(optional=false)
    @Column(name="PROCEDURE")
    private String procedure;

    @Temporal(TemporalType.DATE)
    @Basic
    @Column(name="VALID_FROM")
    private Date validFrom;

    @Temporal(TemporalType.DATE)
    @Basic
    @Column(name="VALID_TO")
    private Date validTo;

    public Authenticator()
    {
    }

    ...
}

PK课:

public class AuthenticatorPk implements Serializable
{
    private static final long serialVersionUID = 1L;

    private Integer id;

    private String authenticator = "";

    public AuthenticatorPk()
    {
    }

    ...
}

启动服务器时,我从Hibernate收到一个映射异常:

00:37:53,979 INFO  [org.hibernate.Version] (ServerService Thread Pool -- 72) HHH000412: Hibernate Core {5.3.6.Final}
00:37:53,980 INFO  [org.hibernate.cfg.Environment] (ServerService Thread Pool -- 72) HHH000206: hibernate.properties not found
00:37:54,088 INFO  [org.hibernate.annotations.common.Version] (ServerService Thread Pool -- 72) HCANN000001: Hibernate Commons Annotations {5.0.4.Final}
00:37:54,382 INFO  [org.hibernate.validator.internal.util.Version] (MSC service thread 1-2) HV000001: Hibernate Validator 6.0.13.Final
00:37:54,621 INFO  [org.jboss.weld.Version] (MSC service thread 1-3) WELD-000900: 3.0.5 (Final)
00:37:54,674 INFO  [org.jboss.as.clustering.infinispan] (ServerService Thread Pool -- 72) WFLYCLINF0002: Started client-mappings cache from ejb container
00:37:54,777 INFO  [org.jboss.as.jpa] (ServerService Thread Pool -- 72) WFLYJPA0010: Starting Persistence Unit (phase 2 of 2) Service 'mappingbug.war#BBStatsPU'
00:37:54,806 INFO  [org.hibernate.dialect.Dialect] (ServerService Thread Pool -- 72) HHH000400: Using dialect: org.hibernate.dialect.MySQL57Dialect
00:37:54,859 INFO  [org.hibernate.envers.boot.internal.EnversServiceImpl] (ServerService Thread Pool -- 72) Envers integration enabled? : true
00:37:55,007 ERROR [org.jboss.msc.service.fail] (ServerService Thread Pool -- 72) MSC000001: Failed to start service jboss.persistenceunit."mappingbug.war#BBStatsPU": org.jboss.msc.service.StartException in service jboss.persistenceunit."mappingbug.war#BBStatsPU": javax.persistence.PersistenceException: [PersistenceUnit: BBStatsPU] Unable to build Hibernate SessionFactory
    at org.jboss.as.jpa.service.PersistenceUnitServiceImpl$1$1.run(PersistenceUnitServiceImpl.java:195)
    at org.jboss.as.jpa.service.PersistenceUnitServiceImpl$1$1.run(PersistenceUnitServiceImpl.java:125)
    at org.wildfly.security.manager.WildFlySecurityManager.doChecked(WildFlySecurityManager.java:650)
    at org.jboss.as.jpa.service.PersistenceUnitServiceImpl$1.run(PersistenceUnitServiceImpl.java:209)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1985)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1487)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1378)
    at java.lang.Thread.run(Thread.java:748)
    at org.jboss.threads.JBossThread.run(JBossThread.java:485)
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: BBStatsPU] Unable to build Hibernate SessionFactory
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.persistenceException(EntityManagerFactoryBuilderImpl.java:1016)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:942)
    at org.jboss.as.jpa.hibernate5.TwoPhaseBootstrapImpl.build(TwoPhaseBootstrapImpl.java:44)
    at org.jboss.as.jpa.service.PersistenceUnitServiceImpl$1$1.run(PersistenceUnitServiceImpl.java:167)
    ... 9 more
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: net.bbstats.entity.MobileTan column: ID (should be mapped with insert="false" update="false")
    at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:862)
    at org.hibernate.mapping.PersistentClass.checkPropertyColumnDuplication(PersistentClass.java:880)
    at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:902)
    at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:634)
    at org.hibernate.mapping.RootClass.validate(RootClass.java:267)
    at org.hibernate.boot.internal.MetadataImpl.validate(MetadataImpl.java:347)
    at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:466)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:939)
    ... 11 more

00:37:55,011 ERROR [org.jboss.as.controller.management-operation] (Controller Boot Thread) WFLYCTL0013: Operation ("deploy") failed - address: ([("deployment" => "mappingbug.war")]) - failure description: {"WFLYCTL0080: Failed services" => {"jboss.persistenceunit.\"mappingbug.war#BBStatsPU\"" => "javax.persistence.PersistenceException: [PersistenceUnit: BBStatsPU] Unable to build Hibernate SessionFactory
    Caused by: javax.persistence.PersistenceException: [PersistenceUnit: BBStatsPU] Unable to build Hibernate SessionFactory
    Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: net.bbstats.entity.MobileTan column: ID (should be mapped with insert=\"false\" update=\"false\")"}}
00:37:55,018 INFO  [org.jboss.as.server] (ServerService Thread Pool -- 42) WFLYSRV0010: Deployed "mappingbug.war" (runtime-name : "mappingbug.war")
00:37:55,019 INFO  [org.jboss.as.controller] (Controller Boot Thread) WFLYCTL0183: Service status report
WFLYCTL0186:   Services which failed to start:      service jboss.persistenceunit."mappingbug.war#BBStatsPU": javax.persistence.PersistenceException: [PersistenceUnit: BBStatsPU] Unable to build Hibernate SessionFactory
WFLYCTL0448: 19 additional services are down due to their dependencies being missing or failed
00:37:55,079 INFO  [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0212: Resuming server
00:37:55,081 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
00:37:55,081 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990
00:37:55,082 ERROR [org.jboss.as] (Controller Boot Thread) WFLYSRV0026: WildFly Full 14.0.1.Final (WildFly Core 6.0.2.Final) started (with errors) in 14146ms - Started 449 of 645 services (23 services failed or missing dependencies, 328 services are lazy, passive or on-demand)

顺便说一句,我在Wildfly 14上使用的是Hibernate Core 5.3.6.Final。

问题

这是怎么了?为何Hibernate对此有所抱怨?我的印象是,这完全是合法的。

其他可能性是这是一个Hibernate错误吗??

注意,我更想知道这是否应该在Hibernate中起作用。我对任何非JPA替代映射都不感兴趣。我可以通过将只读的东西放到关系上来解决这个问题,然后就可以了,但是上面描述的映射不起作用,应该...:-)

1 个答案:

答案 0 :(得分:4)

通过此映射,我几乎可以得到您想要的东西:

    @Entity
@Table(name = "mobile_tan")
public class MobileTan  {
    private static final long serialVersionUID = 1L;
    @Id
    @Column(name="ID", insertable=false, updatable=false)
    private Integer id;    // dupe read-only field, ID already in relationship below (writable)


    @Basic(optional=false)
    @Column(name="MOBILE_PHONE_NBR")
    private String mobilePhoneNbr;


    @Basic
    @Column(name="ACTIVATION_CODE")
    private String activationCode;


    @Basic
    @Column(name="ACTIVATION_ERROR")
    private Boolean activationError = Boolean.FALSE;


    @Basic
    @Column(name="STATUS")
    private Integer status;


    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    @JoinColumn(name="AUTH_NAME", referencedColumnName="AUTHENTICATOR")
    private Authenticator authenticator;
}

但是,这将创建具有以下定义(HSQL)的表

    create table mobile_tan (
    AUTH_NAME integer not null,
    ACTIVATION_CODE varchar(255),
    ACTIVATION_ERROR boolean,
    MOBILE_PHONE_NBR varchar(255) not null,
    STATUS integer,
    primary key (AUTH_NAME)
)

当我运行以下代码时:

    @Test
public void createMobileTan() {
    MobileTan tan = createMobileTan();
    session.flush();
    System.err.println("Tan:  " + tan.getId());
    System.err.println("Authenticator:  " + tan.getAuthenticator().getId());
    System.err.println("AUTH COLUMN:  " + session.createSQLQuery("select AUTH_NAME from mobile_tan").addScalar("AUTH_NAME", IntegerType.INSTANCE).setMaxResults(1).uniqueResult());
    System.err.println("AUTH COLUMN:  " + session.createSQLQuery("select ID from mobile_tan").addScalar("ID", IntegerType.INSTANCE).setMaxResults(1).uniqueResult());
}

public MobileTan createMobileTan() {
    Authenticator auth = createAuthenticator();

    MobileTan tan = new MobileTan();

    tan.setAuthenticator(auth);
    tan.setMobilePhoneNbr("0123456");

    session.persist(tan);

    return tan;
}

private Authenticator createAuthenticator() {
    Authenticator auth = new Authenticator();

    session.persist(auth);

    return auth;
}

我得到以下输出:

Tan:  1
Authenticator:  1
AUTH COLUMN:  1


java.sql.SQLSyntaxErrorException: user lacks privilege or object not found: ID in statement [select ID from mobile_tan limit ?]
    at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
    at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
    at org.hsqldb.jdbc.JDBCPreparedStatement.<init>(Unknown Source)
    at org.hsqldb.jdbc.JDBCConnection.prepareStatement(Unknown Source)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$5.doPrepare(StatementPreparerImpl.java:146)
    at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:172)

使用也是外键的主键创建表。如您所见,它正确设置了对象关系,我可以使用MobileTan类中的getId()方法来访问它。但是,正如您还可以在表定义DDL中看到的那样,数据库中实际上仅创建一列。您是否真的需要2个物理列?然后,您可以决定只使用一列,也可以将此单列用作外键。然后删除多余的列。

我使用以下链接中的信息创建了示例:set-primary-key-and-foreign-key-on-a-single-column