Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Hibernate insert a parent row with a foreign key without inserting the child row?

I'm hoping someone has run into this problem before and can help me out. Basically, Hibernate is inserting a parent row (with an ID pointing to a child row), but not inserting that child row with the associated ID, which leaves the database in a bad state. Here's an example of the exception that's thrown when Hibernate tries to load the improperly saved object:

27 Jun 2011 13:55:31,380 ERROR [scheduler_Worker-4] - 
Job DEFAULT.queryScrubJobDetail threw an unhandled Exception: 
org.springframework.scheduling.quartz.JobMethodInvocationFailedException: 
Invocation of method 'doIt' on target class [XXX] failed; nested exception is
org.springframework.orm.hibernate3.HibernateObjectRetrievalFailureException: 
No row with the given identifier exists: 
[XXX.DataProviderTransaction#60739703]; nested exception is org.hibernate.ObjectNotFoundException: 
No row with the given identifier exists: 
[com.idology.persist.DataProviderTransaction#2]

This part of the application has three entities:

  • Query, which is a parent of DataProviderTransactionReference and DataProviderTransaction
  • DataProviderTransaction, which is a child of Query and a parent of DataProviderTransactionReference
  • DataProviderTransactionReference, which has foreign keys pointing to DataProviderTransaction and Query

Here are the mappings:

From Query:

@OneToMany(mappedBy = "query", cascade =
    { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY)
@Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
@JoinColumn(name = "query_id")
public List<DataProviderTransactionReference> getDataProviderTransactionReferences()

From DataProviderTransaction:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "query_id")
public Query getQuery()

From DataProviderTransactionReference:

@ManyToOne(cascade =
    { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.EAGER)
@JoinColumn(name = "data_provider_transaction_id")
@Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
public DataProviderTransaction getDataProviderTransaction()
{
    return mDataProviderTransaction;
}

The schema looks like this (leaving out the queries table since it has no foreign keys):

data_provider_transaction

+------------------+---------------+------+-----+---------+----------------+
| Field            | Type          | Null | Key | Default | Extra          |
+------------------+---------------+------+-----+---------+----------------+
| id               | bigint(20)    | NO   | PRI | NULL    | auto_increment |
| query_id         | bigint(20)    | YES  | MUL | NULL    |                |
+------------------+---------------+------+-----+---------+----------------+

data_provider_txn_refs

+------------------------------+------------+------+-----+---------+----------------+
| Field                        | Type       | Null | Key | Default | Extra          |
+------------------------------+------------+------+-----+---------+----------------+
| id                           | bigint(20) | NO   | PRI | NULL    | auto_increment |
| created_at                   | datetime   | YES  |     | NULL    |                |
| data_provider_transaction_id | bigint(20) | YES  | MUL | NULL    |                |
| query_id                     | bigint(20) | YES  | MUL | NULL    |                |
+------------------------------+------------+------+-----+---------+----------------+

So once we're done running a query (represented by the Query object), we save it using Spring and Hibernate using the following:

getHibernateTemplate().saveOrUpdate(aQuery);

The Query is saved along with the associated DataProviderTransaction and DataProviderTransactionReference entities. Except that sometimes it saves a Query and a DataProviderTransactionReference without the associated DataProviderTransaction. It does put an ID in the data_provider_transaction_id but it points to a row that does not exist in the data_provider_transaction table.

The next step is to add a foreign key constraint to cause the problem to occur when we do the initial save rather than when we try to load the object later.

We're using Spring 2.5.6, Hibernate 3.3.2, and MySQL 5.0. I've seen the problem occur over the years with earlier versions of Spring and Hibernate, though.

Anyone ever seen/solved this problem?

like image 344
Rafe Avatar asked Jun 27 '11 14:06

Rafe


1 Answers

This sounds like a problem with your id allocation with MySQL. Hibernate can get confused if the generators are not declared correctly, or if you are doing strange things with your code.

Do you have orphan DataProviderTransactions or DataProviderTransactionReferences, A DataProviderTransaction which has a query id which does not exist, or point to the wrong Query?

Are your generators declared as identity for your ids? (see Chapter 5. Basic O/R Mapping, section 5.1.4 id. Normally, this should be enough, but there may be other things you're doing which confuse hibernate.

So, for instance:

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)

To really track this down, you need to know why this is happening, who is inserting these rows. You need a foreign key constraint in the database. It's also possible that something is deleting the DataProviderTransaction and the database isn't complaining because there is no foreign key.

like image 134
Matthew Farwell Avatar answered Nov 04 '22 00:11

Matthew Farwell