Can one-to-one shared primary key mapping in Hibernate be optional/null?

I have seen quite a few examples/tutorials on the web for doing one-one shared primary key mapping using Hibernate annotations. However in all the examples that I have seen it seems the mapping can't be optional or if it is optional it can't be on the primary key. I have the following situation where I have a one-one primary key mapping between a Company table and a CompanyDocs table. The CompanyDocs table is a weak entity, i.e although a Company object exists, it necessarily won't have a CompanyDocs entity linked from the start, however this may be created later on.

The Company object are created and persisted by an external application, however updated using the Hibernate application. This is what I have..(Please note this is not the actual example)

Company table
- companyid (Pk)
- name
- desc
- test1
- test2

CompanyDocs table
- docsid (Pk) (Fk) 
- detail1
- detail2
- detail3

Model Classes

Company.java
@Entity
@Table(name = "company", schema = "public")   
public class Company implements java.io.Serializable {

  private String companyid
  private CompanyDocs companydocs;

  @Id
  @Column(name = "companyid", unique = true, nullable = false, length = 20)
  public String getCompanyid() {
    return this.companyid;
}

  public void setCompanyid(String companyid) {
    this.companyid = companyid;
}

  @OneToOne(fetch = FetchType.LAZY, mappedBy = "companydocs")
  @NotFound(action = NotFoundAction.IGNORE)
  @PrimaryKeyJoinColumn(name = "companyid", referencedColumnName = "docsid")
  public CompanyDocs getCompanydocs() {
    return this.companydocs;
}

  public void setCompanydocs(CompanyDocs companydocs) {
    this.companydocs = companydocs;
} 

}

CompanyDocs.java

@Entity
@Table(name = "companydocs", schema = "public")
public class CompanyDocs implements java.io.Serializable {

    private String docsid;
    private Company company;

    @GenericGenerator(name = "generator", strategy = "foreign", parameters =  
    @Parameter(name = "property", value = "company"))
    @Id
    @GeneratedValue(generator = "generator")
    @Column(name = "docsid", unique = true, nullable = false, length = 20)
    public String getDocsid() {
        return this.docsid;
    }

    public void setDocsid(String docsid) {
        this.docsid = docsid;
    }


    @PrimaryKeyJoinColumn(name = "docsid", referencedColumnName = "companyid")      
    @OneToOne(fetch = FetchType.LAZY)
    @NotFound(action = NotFoundAction.IGNORE)
    public Company getCompany() {
        return this.company;
    }

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

CompanyDAOImpl.java

public Company getCompany(String companyid) {
Criteria c = sessionFactory.getCurrentSession().createCriteria(Company.class)
                 .add(Restrictions.eq("companyid", companyid));     
    try {       
        return (Company) c.list().get(0);
    }
    catch(IndexOutOfBoundsException e) {        
        logger.info("GetCompany threw exception " +e);
        return null;
    }       

}

public void storeCompany(Company c) {
sessionFactory.getCurrentSession().saveOrUpdate(c);
 }

public void storeCompanyDocs(CompanyDocs cd) {
sessionFactory.getCurrentSession().saveOrUpdate(cd);
 }

public CompanyDocs getCompanyDocs(String companyid) {       
    try{
       Criteria c = sessionFactory.getCurrentSession()
      .createCriteria(CompanyDocs.class).add(Restrictions.eq("companyid", companyid));
        logger.info("getCompanyDocsreturned"+c.list().get(0));
        return (CompanyDocs)c.list().get(0);            

    }catch(IndexOutOfBoundsException e){
        logger.info("getCompanyDocs threw exception " +e);
        return null;            
    }

}

CompanyServiceImpl.java

 @Transactional(propagation = Propagation.REQUIRED, readOnly= false,       
 noRollbackFor=IllegalArgumentException.class)
 @ExceptionHandler(NullPointerException.class)

 public CompanyResponse updateCompany(Service parameters) throws SOAPException { 
 LOG.info("In updateCompany");
 Company c = companyDao.getCompany(parameters.getId());
 //CompanyDocs cd = companyDao.getCompanyDocs(parameters.getId());
 CompanyDocs cd = c.getCompanyDocs();       
  LOG.info("CompanyDocs object is:- " + cd);
    if(cd == null)
      {
        cd = new CompanyDocs(parameters.getId());
        c.setCompanyDocs(cd);
            cd.setCompany(c);
            LOG.info("CompanyDocs object created new :- " + cd);
            companyDao.storeCompanyDocs(cd);
        LOG.info("CompanyDocs object stored :- " + cd.getDocsid());     
      } 

        c.setDetail1();
        c.setDetail2();
        c.setDetail3();
        .............
        companyDao.storeCompany(c)
        LOG.info("Stored Company");
        LOG.info("CompanyService Response Return------:")
        return new CompanyResponse();

 }

My log file prints this.

   2012-05-02 22:57:18,951 INFO [au.com.CompanyServiceImpl] - <In updateCompany>
   2012-05-02 22:57:18,975 INFO [au.com.CompanyDAOImpl] - <getCompany returned        
   au.com.Company@2738ef1d>[au.com.CompanyServiceImpl] - <CompanyDocs object is:- null>
   2012-05-02 22:57:18,991 INFO [au.com.CompanyServiceImpl] - <CompanyDocs object 
   created new :- au.com.CompanyDocs@373ee92>
   2012-05-02 22:57:18,995 INFO [au.com.CompanyServiceImpl] - <CompanyDocs object 
   updated :- CO12345>
   2012-05-02 22:57:19,338 INFO [au.com.CompanyDAOImpl] - <Stored Company >
   2012-05-02 22:57:19,338 INFO [au.com.CompanyServiceImpl] - <CompanyService 
   Response  Return------:>
   2012-05-02 22:57:19,498 ERROR [org.hibernate.jdbc.AbstractBatcher] - <Exception    
   executing batch: >
   org.hibernate.StaleStateException: Batch update returned unexpected row count from  
   update [0]; actual row count: 0; expected: 1 at 
   org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:61)
   at   
   org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:46)
at org.hibernate.jdbc.BatchingBatcher.checkRowCounts(BatchingBatcher.java:68)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:48)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:246)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168)
at org.hibernate.event.def.AbstractFlushingEventListener
    .performExecutions(AbstractFlushingEventListener.java:298)
at org.hibernate.event.def.DefaultFlushEventListener.
    onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at org.springframework.orm.hibernate3.HibernateTransactionManager.
    doCommit(HibernateTransactionManager.java:656)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.
    processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager
     .commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.transaction.interceptor.TransactionAspectSupport
    .commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor
    .invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation
    .proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.
    invoke(JdkDynamicAopProxy.java:202)
at $Proxy28.login(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.
    invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.cxf.service.invoker.AbstractInvoker.
    performInvocation(AbstractInvoker.java:173)
at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:89)
at org.apache.cxf.jaxws.JAXWSMethodInvoker.invoke(JAXWSMethodInvoker.java:60)
at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:75)
at org.apache.cxf.interceptor.ServiceInvokerInterceptor$1.
     run(ServiceInvokerInterceptor.java:58)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at org.apache.cxf.workqueue.SynchronousExecutor.
       execute(SynchronousExecutor.java:37)
at org.apache.cxf.interceptor.ServiceInvokerInterceptor.
     handleMessage(ServiceInvokerInterceptor.java:106)
at org.apache.cxf.phase.PhaseInterceptorChain.
      doIntercept(PhaseInterceptorChain.java:255)
at org.apache.cxf.transport.ChainInitiationObserver.
    onMessage(ChainInitiationObserver.java:113)
at org.apache.cxf.transport.servlet.ServletDestination.
    invoke(ServletDestination.java:97)
at org.apache.cxf.transport.servlet.ServletController.
      invokeDestination(ServletController.java:461)
at org.apache.cxf.transport.servlet.ServletController.
     invoke(ServletController.java:188)
at org.apache.cxf.transport.servlet.AbstractCXFServlet.
     invoke(AbstractCXFServlet.java:148)
at org.apache.cxf.transport.servlet.AbstractHTTPServlet.
     handleRequest(AbstractHTTPServlet.java:179)
at org.apache.cxf.transport.servlet.
     AbstractHTTPServlet.doPost(AbstractHTTPServlet.java:103)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
at org.apache.cxf.transport.servlet.AbstractHTTPServlet.
    service(AbstractHTTPServlet.java:159)
at org.apache.catalina.core.ApplicationFilterChain.
    internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.
    doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.
      invoke(StandardWrapperValve.java:233) 
at org.apache.catalina.core.StandardContextValve.
     invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.
    invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:859)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.
     process(Http11Protocol.java:588)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
at java.lang.Thread.run(Thread.java:662)
   2012-05-02 22:57:19,501
  ERROR [org.hibernate.event.def.AbstractFlushingEventListener]     
  -   <Could not synchronize database state with session>

I have tried using @non annotation,optional = false/true, also tried using a uni-directional mapping and loading the companydocs object using criteria. But it seems Hibernate always excepts a CompanyDocs object for an existing Company..

Strangely enough, this is used to work fine, when we were working with an earlier version of hibernate and were using mapping files , instead of annotaions. It was a shared key mapping but unidirectional so the Company hbm file had no mapping of CompanyDocs object. I am using hibernate 3.3.1.GA.jar for annotations. Is there something I have done wrong on my side or is this not possible anymore with the new version of Hibernate?

Answers


@PrimaryKeyJoinColumn should only be used in the optional side , so remove @PrimaryKeyJoinColumn that is annotated on the Company.

Besides , mappedBy attribute of the @OneToOne should refer to the property name of the entity that @OneToOne is marked on. So it should be :

@OneToOne(fetch = FetchType.LAZY, mappedBy = "company")

The problem was Hibernate was issuing an update , instead of an insert command for the companydocs object. This is because if the primary key for the object is null then Hibernate will do inserts, if it is not null it will do an update and in my case as I was setting the key in the implementation class as below, it was doing an update.

Original Code

if(cd == null)
  {
    cd = new CompanyDocs(parameters.getId());
    c.setCompanyDocs(cd);
    cd.setCompany(c);
    LOG.info("CompanyDocs object created new :- " + cd);
    companyDao.storeCompanyDocs(cd);
    LOG.info("CompanyDocs object stored :- " + cd.getDocsid());     
  } 

New Code

if(cd == null)
  {
    cd = new CompanyDocs();//no-arg constructor
    c.setCompanyDocs(cd);
    cd.setCompany(c);
    LOG.info("CompanyDocs object created new :- " + cd);
    companyDao.storeCompanyDocs(cd);
    LOG.info("CompanyDocs object stored :- " + cd.getDocsid());     
  } 

Worked beautifully !!

I found this link useful for fixing this issue

Hibernate StaleStateException


Need Your Help

Where to put try catch

c# .net

Consider this scenario:

Textbox control like “TO” field in outlook

c# .net winforms textbox

I'm looking for a Textbox control that works similar to the email recipient textboxes in Outlook (the To, Cc and Bcc inputs). It shall have below characteristics.

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.