Hi Emmanuel,

I think we might have found a bug in BindHelper.bindPropertiesByColumns, want to run by you to see if it's worth while submitting to Jira.

What we are trying to do is to associate two attributes of the same type to an object.

i.e.,

class Deal {
  @ManyToOne
  @JoinColumn(name = "a_customer_id", referencedColumnName = "customer_id", nullable = true)
  Customer a;
 
 @ManyToOne
 @JoinColumn(name = "b_customer_id", referencedColumnName = "customer_id", nullable = true)
 Customer b;
}

// note: customer id is not a pk
class Customer {
  @Column(name = "customer_id")
  String customerId;  
}

When loading deal object, customer a is always populated correctly but customer b will only be populated occasionally (depending on your luck!).

After digging through Hibernate annotation source, I think I've found the cause of the problem in BindHelper.bindPropertiesByColumns where HashSet is being used.

From looking through the source, my understanding is that hibernate will create a synthetic property for each of the customer Deal.a and Deal.b and add it to a list of properties type mapping within Customer persistent class object. This type mapping is then used to hydrate Deal.a and Deal.b when Deal object is being resolved.

When synthetic property for Deal.a is being created, the propertymapping is straight forward because only customerId property is available in the Customer persistent class. Deal.a is then mapped simply to a simple type of Customer.customerId.

Subsequently, when synthetic property for Deal.b is being created (BindHelper:97), the property mapping has two possible values - one is the Customer.customerId, the other is the recently created synthetic mapping (_Deal_a). These two values are extracted out and put into a HashSet (BindHelper.bindPropertiesByColumn:190). The hashset is then the being iterated through to return the first value of iterator (which the order is not guaranteed). Obviously it would be wrong to create a synthetic property for Deal.b with synthetic property _Deal_a type mapping (which is what occasionally happens).

Upon load, hibernate core will then attempt to hydrate Deal.a by instantiating Customer (_Deal_a) type object and inject a_customer_id into an instance of _Deal_a type. This will result in a correct load.

When hibernate core attempt to hydrate Deal.b, it will instantiate Customer (_Deal_b) type object. However, _Deal_b type object mapps to an embedded _Deal_a type. So it will then recurse through the embedded type, instantiate Customer (_Deal_a) type object, assign b_customer_id into embedded Customer.customerId after which instance of Customer(_Deal_a) will be assigned to Customer.customerId (_Deal_b). This assignment is done via EmbeddedPropertyAccessor.EmbeddedSetter.set method - which looks like a null implementation. The result is that Deal.b won't be set and no errors is being reported at all. What worst is that the system can appear to have been working most of the time, but if you add additional attribute to class Deal to disturb the balance of the hashcode generation, then all of the sudden it won't work.

I can think of a couple of fixes in BindHelper.bindPropertiesByColumns:
1. Instead of using HashSet we use an ordered set to guarantee the order, this way the native properties will always be returned first.
2. Use List instead of Set to guarantee the order - the values being added in to the columnsToProperty map can never be null or repeated anyway.
3. Filter out all of the synthetic properties from columnsToProperty mapping (this propbably wouldn't help for muti column mapping?).

BTW: It also perplex me why EmbeddedPropertyAccessor.EmbeddedSetter.set method would do nothing and report no error when it can't set the property.

Sorry for the lengthy email but it's a fairly subtle bug that can't always be replicated so I thought it's worth while explaining in details. Let me know if you want me to submit this to Jira. We have opted for option 2 on local fix and it appear to be working well.

Regards,
rOnn c.

##########################################################
DISCLAIMER:
This email and any attachment may contain confidential information.
If you are not the intended recipient you are not authorized to copy
or disclose all or any part of it without the prior written consent
of Toyota.

Opinions expressed in this email and any attachments are those of the
sender and not necessarily the opinions of Toyota.
Please scan this email and any attachment(s) for viruses.
Toyota does not accept any responsibility for problems caused by
viruses, whether it is Toyota's fault or not.
##########################################################

Reply via email to