[hibernate-issues] [Hibernate-JIRA] Commented: (HHH-2983) Properties of a Map key not consistently populated before being put into the Map

Christoph Jäger (JIRA) noreply at atlassian.com
Thu Jun 25 08:48:34 EDT 2009


    [ http://opensource.atlassian.com/projects/hibernate/browse/HHH-2983?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=33472#action_33472 ] 

Christoph Jäger commented on HHH-2983:
--------------------------------------

Dobes,

does changing your equals() method solve the problem? I have a problem quite similar to yours, I do not use getClass(), and this issue's title "Properties of a Map key not consistently populated before being put into the Map" perfectly describes it. I think this issue is not solved!

> Properties of a Map key not consistently populated before being put into the Map
> --------------------------------------------------------------------------------
>
>                 Key: HHH-2983
>                 URL: http://opensource.atlassian.com/projects/hibernate/browse/HHH-2983
>             Project: Hibernate Core
>          Issue Type: Bug
>          Components: core
>    Affects Versions: 3.2.5
>         Environment: Glassfish v2 b58, PostgreSQL 8.2, running in Windows XP
>            Reporter: Dobes Vandermeer
>
> I'm getting weird behavior where my map correctly has two entries when fetched as part of a list, and only one entry when I fetch it using EntityManager.get().
> Here's an abbreviated version of the mapping for the class that contains the Map:
> @Entity(name="Account")
> public class Account implements Comparable<Account>, SecurityChecks {
> 	private Long id;
> 	private Map<Currency, Balance> balances = new HashMap<Currency, Balance>(); // Current balance in each currency
> 	
> 	public Account() {
> 	}
> 	@Id @GeneratedValue
> 	public Long getId() {
> 		return id;
> 	}
> 	public void setId(Long id) {
> 		this.id = id;
> 	}
> 	@OneToMany(cascade={CascadeType.ALL}, mappedBy="account")
> 	@MapKey(name="currency")
> 	public Map<Currency, Balance> getBalances() {
> 		return balances;
> 	}
> 	public void setBalances(Map<Currency, Balance> balances) {
> 		this.balances = balances;
> 	}
> }
> And the class that it contains:
> @Entity(name="Balance")
> @Table(uniqueConstraints={@UniqueConstraint(columnNames={ "account_id", "currency_id" })})
> public class Balance implements SecurityChecks {
> 	private Long id;
> 	private Account account;
> 	private Currency currency;
> 	private long amount;
> 	
> 	@ManyToOne(cascade=CascadeType.PERSIST, optional=false)
> 	public Account getAccount() {
> 		return account;
> 	}
> 	public void setAccount(Account account) {
> 		this.account = account;
> 	}
> 	
> 	@Column(nullable=false)
> 	public long getAmount() {
> 		return amount;
> 	}
> 	public void setAmount(long amount) {
> 		this.amount = amount;
> 	}
> 	
> 	@ManyToOne(cascade=CascadeType.PERSIST, optional=false)
> 	@JoinColumn(name="currency_id")
> 	public Currency getCurrency() {
> 		return currency;
> 	}
> 	public void setCurrency(Currency currency) {
> 		this.currency = currency;
> 	}
> 	
> 	@Id @GeneratedValue
> 	public Long getId() {
> 		return id;
> 	}
> 	public void setId(Long id) {
> 		this.id = id;
> 	}
> }
> And a class that has a list of Accounts:
> @Entity(name="Business")
> public class Business implements SecurityChecks {
> 	private Long id;
> 	private String name;
> 	private List<Account> accounts = new ArrayList<Account>();
> 	@Id @GeneratedValue
> 	public Long getId() {
> 		return id;
> 	}
> 	public void setId(Long id) {
> 		this.id = id;
> 	}
> 	@OneToMany(mappedBy="business", cascade={CascadeType.ALL})
> 	public List<Account>  getAccounts() {
> 		return accounts;
> 	}
> 	public void setAccounts(List<Account>  accounts) {
> 		this.accounts = accounts;
> 	}
> 	
> 	@Column(nullable=false)
> 	public String getName() {
> 		return name;
> 	}
> 	public void setName(String name) {
> 		this.name = name;
> 	}
> }	
> Here's the definition of Currency:
> package com.habitsoft.books.model;
> import javax.persistence.Entity;
> import javax.persistence.GeneratedValue;
> import javax.persistence.Id;
> import javax.persistence.Transient;
> import com.habitsoft.books.service.client.CurrencyFormatter;
> @Entity(name="Currency")
> public class Currency implements Comparable<Currency> {
> 	private Long id;
> 	private String currencyCode;
> 	private String name;
> 	private String prefix = "";
> 	private String suffix = "";
> 	private int decimalPlaces;
> 	@Id
> 	@GeneratedValue
> 	public Long getId() {
> 		return id;
> 	}
> 	public void setId(Long id) {
> 		this.id = id;
> 	}
> 	
> 	@Override
> 	public int hashCode() {
> 		final int PRIME = 31;
> 		int result = 1;
> 		result = PRIME * result + ((getCurrencyCode() == null) ? 0 : getCurrencyCode().hashCode());
> 		return result;
> 	}
> 	@Override
> 	public boolean equals(Object obj) {
> 		if (this == obj)
> 			return true;
> 		if (obj == null)
> 			return false;
> 		if (getClass() != obj.getClass())
> 			return false;
> 		final Currency other = (Currency) obj;
> 		if (getCurrencyCode() == null) {
> 			if (other.getCurrencyCode() != null)
> 				return false;
> 		} else if (!getCurrencyCode().equals(other.getCurrencyCode()))
> 			return false;
> 		return true;
> 	}
> 	public int compareTo(Currency obj) {
> 		if (this == obj)
> 			return 0;
> 		if (obj == null)
> 			return -1;
> 		final Currency other = (Currency) obj;
> 		return getCurrencyCode().compareTo(other.getCurrencyCode());
> 	}
> 	public String getCurrencyCode() {
> 		return currencyCode;
> 	}
> 	public void setCurrencyCode(String currencyCode) {
> 		this.currencyCode = currencyCode;
> 	}
> 	public String getName() {
> 		return name;
> 	}
> 	public void setName(String name) {
> 		this.name = name;
> 	}
> 	
> }
> Here's a line of code I use to fetch the Account directly:
> return em.find(Account.class, accountId);
> Here's how I fetch the list of accounts for a business:
> Business business = em.find(Business.class, businessId);
> return business.getAccounts().subList(offset, offset+limit);
> What is very, very odd to me is that the Accounts returned by the first form have just a single Balance instance in the map, when there should be two, whereas the ones returned by the second form have both entries I'm expecting.
> Hibernate prints the following SQL queries:
> Hibernate: select account0_.id as id270_0_, account0_.business_id as business5_270_0_, account0_.description as descript2_270_0_, account0_.name as name270_0_, account0_.type as type270_0_ from Account account0_ where account0_.id=?
> Hibernate: select balances0_.account_id as account3_2_, balances0_.id as id2_, balances0_.currency_id as formula5_2_, balances0_.id as id271_1_, balances0_.account_id as account3_271_1_, balances0_.amount as amount271_1_, balances0_.currency_id as currency4_271_1_, currency1_.id as id275_0_, currency1_.currencyCode as currency2_275_0_, currency1_.decimalPlaces as decimalP3_275_0_, currency1_.name as name275_0_, currency1_.prefix as prefix275_0_, currency1_.suffix as suffix275_0_ from Balance balances0_ inner join Currency currency1_ on balances0_.currency_id=currency1_.id where balances0_.account_id=?
> If I execute these queries against the database manually, the correct number of results is returned, so it seems likely that hibernate is generating the correct query, but then populating the map incorrectly.
> I took a guess that hashCode() and equals() in Currency were the most likely cause of the issue and indeed if I change them to use id instead of currencyCode the problem goes away.
> So, for some odd reason hibernate is populating the id field and not the other fields, and then putting it into the Map, but only when the object is fetched directly.  This seems to point to some inconsistency in the way hibernate is initializing the objects and maps, somewhere.
> The workaround is to not use any fields except id in hashCode() and equals() for an entity used as a map key, and to ensure you call persist() on the key objects before putting them into the map.  This seems like a reasonable constraint to me, but it would be nice if the behavior was more consistent to avoid excessive head-scratching.

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://opensource.atlassian.com/projects/hibernate/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

       




More information about the hibernate-issues mailing list