ebean: @OneToOne with @JoinColumn to non primary key throws `Data conversion error`

import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.EbeanServerFactory;
import com.avaje.ebean.config.DataSourceConfig;
import com.avaje.ebean.config.ServerConfig;
import com.google.common.collect.Lists;
import org.avaje.agentloader.AgentLoader;
import org.h2.Driver;
import org.junit.Before;
import org.junit.Test;

import javax.persistence.*;
import java.io.IOException;
import java.lang.instrument.IllegalClassFormatException;

/**
 * @author icode
 */
public class EbeanTest {
    EbeanServer server;

    @Before
    public void init() {
        AgentLoader.loadAgentFromClasspath("avaje-ebeanorm-agent", "debug=5");
        final ServerConfig config = new ServerConfig();
        config.setClasses(Lists.<Class<?>>newArrayList(RoadShowMsg.class, Company.class));
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setUrl("jdbc:h2:file:./db");
        dataSourceConfig.setDriver(Driver.class.getName());
        dataSourceConfig.setUsername("sa");
        dataSourceConfig.setPassword("");
        config.setDataSourceConfig(dataSourceConfig);
        config.setName("default");
        config.setDdlGenerate(true);
        config.setDdlRun(true);
        config.setDefaultServer(true);
        server = EbeanServerFactory.create(config);
    }

    @Test
    public void JsonTest() throws IOException, IllegalClassFormatException, ClassNotFoundException {
        RoadShowMsg msg = new RoadShowMsg();
        Company company = new Company();
        company.setCorpId("corp_id_1000000");
        msg.setCompany(company);
        server.save(msg);
        server.find(RoadShowMsg.class, 1);
    }

    @MappedSuperclass
    public static abstract class Model {
        @Id
        @GeneratedValue
        public Long id;

        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }
    }

    @Entity
    public static class RoadShowMsg extends Model {
        @OneToOne(cascade = CascadeType.ALL, optional = false)
        @JoinColumn(name = "corp_id", nullable = false, referencedColumnName = "corp_id")
        public Company company;

        public Company getCompany() {
            return company;
        }

        public void setCompany(Company company) {
            this.company = company;
        }
    }

    @Entity
    public static class Company extends Model {
        @Column(length = 50, unique = true)
        public String corpId;

        public String getCorpId() {
            return corpId;
        }

        public void setCorpId(String corpId) {
            this.corpId = corpId;
        }
    }
}

console:

javax.persistence.PersistenceException: Error loading on EbeanTest$RoadShowMsg.company

    at com.avaje.ebeaninternal.server.query.SqlBeanLoad.load(SqlBeanLoad.java:85)
    at com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne.load(BeanPropertyAssocOne.java:615)
    at com.avaje.ebeaninternal.server.query.SqlTreeNodeBean.load(SqlTreeNodeBean.java:268)
    at com.avaje.ebeaninternal.server.query.CQuery.readNextBean(CQuery.java:431)
    at com.avaje.ebeaninternal.server.query.CQuery.hasNext(CQuery.java:512)
    at com.avaje.ebeaninternal.server.query.CQuery.readBean(CQuery.java:494)
    at com.avaje.ebeaninternal.server.query.CQueryEngine.find(CQueryEngine.java:356)
    at com.avaje.ebeaninternal.server.query.DefaultOrmQueryEngine.findId(DefaultOrmQueryEngine.java:129)
    at com.avaje.ebeaninternal.server.core.OrmQueryRequest.findId(OrmQueryRequest.java:251)
    at com.avaje.ebeaninternal.server.core.DefaultServer.findId(DefaultServer.java:1190)
    at com.avaje.ebeaninternal.server.core.DefaultServer.find(DefaultServer.java:1048)
    at com.avaje.ebeaninternal.server.core.DefaultServer.find(DefaultServer.java:1035)
    at EbeanTest.JsonTest(EbeanTest.java:48)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
Caused by: org.h2.jdbc.JdbcSQLException: Data conversion error converting "corp_id_1000000" [22018-187]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
    at org.h2.message.DbException.get(DbException.java:168)
    at org.h2.value.Value.convertTo(Value.java:902)
    at org.h2.value.Value.getLong(Value.java:443)
    at org.h2.jdbc.JdbcResultSet.getLong(JdbcResultSet.java:650)
    at com.avaje.ebeaninternal.server.type.RsetDataReader.getLong(RsetDataReader.java:121)
    at com.avaje.ebeaninternal.server.type.ScalarTypeLong.read(ScalarTypeLong.java:34)
    at com.avaje.ebeaninternal.server.type.ScalarTypeLong.read(ScalarTypeLong.java:17)
    at com.avaje.ebeaninternal.server.deploy.BeanProperty.read(BeanProperty.java:581)
    at com.avaje.ebeaninternal.server.deploy.id.IdBinderSimple.read(IdBinderSimple.java:177)
    at com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne$Reference.read(BeanPropertyAssocOne.java:749)
    at com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne.read(BeanPropertyAssocOne.java:574)
    at com.avaje.ebeaninternal.server.query.SqlBeanLoad.load(SqlBeanLoad.java:74)
    ... 34 more
Caused by: java.lang.NumberFormatException: For input string: "corp_id_1000000"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Long.parseLong(Long.java:589)
    at java.lang.Long.parseLong(Long.java:631)
    at org.h2.value.Value.convertTo(Value.java:854)
    ... 44 more

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 17 (14 by maintainers)

Commits related to this issue

Most upvoted comments

JPA Spec 11.1.21 Support for referenced columns that are not primary key columns of the referenced table is optional. Applications that use such mappings will not be portable.

So this is optional for JPA providers.

Also note: http://stackoverflow.com/questions/5818373/does-the-jpa-specification-allow-references-to-non-primary-key-columns

Now in terms of Ebean ORM internals … the issue here in trying to support a JoinColumn to a non-primary key is in 2 main areas:

  • Loading check against the persistence context (which is @Id / primary key based)
  • Support for lazy loading (which is @Id / primary key based)

To support unique non-null columns equally with primary key we need to translate between the unique column and the primary key value for both of those features (the persistence context check and the further lazy loading).

That has the potential to be “not good implementation” without introducing some level of compromise (separate treatment in persistence context for example).

Ultimately this this mapping option (if supported) there are now 2 active values used to identify each Company instance (the @Id and corpId) and that translates into a combination of extra complexity, performance costs or compromising functionality.