Timo Ernst
Timo Ernst

Reputation: 15973

Hibernate: Inconsistent primary key generation due to one-to-one relation

I have two one-to-one relations here between a class called "MailAccount" and the classes "IncomingServer" and "OutgoingServer".

(It's a Java application running on Tomcat and Ubuntu server edition).

The mapping looks like this:

MailAccount.hbm.xml

<hibernate-mapping package="com.mail.account">
    <class name="MailAccount" table="MAILACCOUNTS" dynamic-update="true">

        <id name="id" column="MAIL_ACCOUNT_ID">
            <generator class="native" />
        </id>

        <one-to-one name="incomingServer" cascade="all-delete-orphan">
        </one-to-one>
        <one-to-one name="outgoingServer" cascade="all-delete-orphan">
        </one-to-one>

    </class>
</hibernate-mapping>

IncomingMailServer.hbm.xml

<hibernate-mapping>
    <class name="com.IncomingMailServer" table="MAILSERVER_INCOMING" abstract="true">

        <id name="id" type="long" access="field">
            <column name="MAIL_SERVER_ID" />
            <generator class="native" />
        </id>

        <discriminator column="SERVER_TYPE" type="string"/>

        <many-to-one name="mailAccount" column="MAIL_ACCOUNT_ID" not-null="true" unique="true" />

        <subclass name="com.ImapServer" extends="com.IncomingMailServer" discriminator-value="IMAP_SERVER" />           
        <subclass name="com.Pop3Server" extends="com.IncomingMailServer" discriminator-value="POP3_SERVER" />

    </class>
</hibernate-mapping>

OutgoingMailServer.hbm.xml

<hibernate-mapping>
    <class name="com.OutgoingMailServer" table="MAILSERVER_OUTGOING" abstract="true">

        <id name="id" type="long" access="field">
            <column name="MAIL_SERVER_ID" />
            <generator class="native" />
        </id>

        <discriminator column="SERVER_TYPE" type="string"/>

        <many-to-one name="mailAccount" column="MAIL_ACCOUNT_ID" not-null="true" unique="true" />

        <subclass name="com.SmtpServer" extends="com.OutgoingMailServer" discriminator-value="SMTP_SERVER" />

    </class>
</hibernate-mapping>

The class hierarchy looks like this:

public class MailAccount{
 IncomingMailServer incomingServer;
 OutgoingMailServer outgoingServer;
}

public class MailServer{
 HostAddress hostAddress;
 Port port;
}

public class IncomingMailServer extends MailServer{
 // ...
}

public class OutgoingMailServer extends MailServer{
 // ...
}

public class ImapServer extends IncomingMailServer{
 // ...
}

public class Pop3Server extends IncomingMailServer{
 // ...
}

public class SmtpServer extends OutgoingMailServer{
 // ...
}

Now, here comes the problem:

Although most of the time my application runs well, there seems to be one situation in which email servers get deleted, but the corresponding account doesn't and that's when this call is made:

session.delete(mailAccountInstance);

In a one-to-one relation in Hibernate, the primary keys between mail account and its servers must be equal, if not, the relation completely gets out of sync:

Example:

Imagine, the tables are filled with data like this:

Table "MailAccount" (Current auto_increment value: 2)

MAIL_ACCOUNT_ID NAME
0               Account1
1               Account2

Table "IncomingMailServer" (Current auto_increment value: 2)

MAIL_SERVER_ID  MAIL_ACCOUNT_ID
0               0
1               1

Now, image the account with ID=1 gets deleted and new accounts get added. The following then SOMETIMES happens:

Table "MailAccount" (Current auto_increment value: 3)

MAIL_ACCOUNT_ID NAME
0               Account1
1               Account2
2               Account3

Table "IncomingMailServer" (Current auto_increment value: 2)

MAIL_SERVER_ID  MAIL_ACCOUNT_ID
0               0
1               2

This completely messes up my database consistency. How can I avoid this?

Upvotes: 4

Views: 1010

Answers (1)

GeertPt
GeertPt

Reputation: 17846

If you want a shared primary key, you can use the native id generator only once. You create the mail account first, which will generate its own id, but when you create the Incoming- or OutgoingMailServer, these need to take their id from the mailAccount property.

So you need the "foreign" generator:

<class name="OutgoingMailServer">
    <id name="id" column="MAIL_SERVER_ID"> 
       <generator class="foreign"> 
           <param name="property">mailAccount</param> 
       </generator>
    </id>
    <one-to-one name="mailAccount" not-null="true" constrained="true"/>
<class>

You don't need a MAIL_ACCOUNT_ID column, since it will always be identical to the MAIL_SERVER_ID anyway.

Quite basic follow the reference about bidirectional one-to-one association on a primary key.

Upvotes: 4

Related Questions