I'm using Hibernate with a Restrictions.in() criteria on a table with a composite id. Everything works fine when I put just one id in the query but when I put multiple id:s it doesn't work.
The query generated by Hibernate looks like this:
Code:
select this_.part_id as part1_2_0_, this_.player_id as player2_2_0_, this_.timestamp as timestamp2_0_ from user_achievements.part_player this_ where (1=1) and (this_.part_id, this_.player_id) in ((?, ?), (?, ?))
When I ran it manually in a query browser it worked just fine so I turned tracing on in log4j and it turned out that hibernate binds the parameters wrong when you pass in multiple keys.
I pass in a collection of my composite-id class called PlayerPartPk:
Code:
.....
public List<PlayerPart> findAllByPlayerAndPart(List<PlayerPartPk> partPks) {
Criteria criteria = getCriteriaForSearchCriteria(new PlayerPart()).add(Restrictions.in("id", partPks));
return criteria.list();
}
.....
protected final Criteria getCriteriaForSearchCriteria(final T searchCriteria) {
Example example = Example.create(searchCriteria);
Criteria criteria = getSession().createCriteria(persistentClass);
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
return criteria.add(example);
}
Code:
import org.codehaus.jackson.map.annotate.JsonDeserialize;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.hibernate.annotations.Proxy;
import org.hibernate.annotations.Type;
import org.joda.time.DateTime;
import javax.persistence.*;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.io.Serializable;
@XmlRootElement
@XmlAccessorType(XmlAccessType.PROPERTY)
@Entity
@Proxy(lazy = false)
@Table(name = "part_player", schema = "user_achievements")
public class PlayerPart implements Serializable {
private static final String PERSISTENT_DATE_TIME = "org.joda.time.contrib.hibernate.PersistentDateTime";
private PlayerPartPk id;
private DateTime timestamp;
@EmbeddedId
public PlayerPartPk getId() {
return id;
}
public void setId(PlayerPartPk playerPartPk) {
this.id = playerPartPk;
}
@Column(name ="timestamp", nullable = true)
@XmlJavaTypeAdapter(DateTimeXmlAdapter.class)
@JsonSerialize(using = DateTimeJsonSerializer.class)
@JsonDeserialize(using = DateTimeJsonDeserializer.class)
@Type(type = PERSISTENT_DATE_TIME)
public DateTime getTimestamp() {
return timestamp;
}
public void setTimestamp(DateTime timestamp) {
this.timestamp = timestamp;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PlayerPart that = (PlayerPart) o;
if (this.getId() != null ? !this.getId().equals(that.getId()) : that.getId() != null) return false;
return true;
}
@Override
public int hashCode() {
return this.getId() != null ? this.getId().hashCode() : 0;
}
}
Code:
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class PlayerPartPk implements Serializable {
private Integer partId;
private Integer playerId;
@Column(name ="part_id", nullable = false)
public Integer getPartId() {
return partId;
}
public void setPartId(Integer partId) {
this.partId = partId;
}
@Column(name ="player_id", nullable = false)
public Integer getPlayerId() {
return playerId;
}
public void setPlayerId(Integer playerId) {
this.playerId = playerId;
}
@Override
public boolean equals(Object other) {
boolean result = true;
if(!(other instanceof PlayerPartPk)) {
return false;
}
Integer otherPartId = ((PlayerPartPk)other).getPartId();
Integer otherPlayerId = ((PlayerPartPk)other).getPlayerId();
if(this.getPartId() == null || otherPartId == null) {
result = false;
} else {
result = this.getPartId().equals(otherPartId);
}
if(this.getPlayerId() == null || otherPlayerId == null) {
result = false;
} else {
result = this.getPlayerId().equals(otherPlayerId);
}
return result;
}
@Override
public int hashCode() {
int code = 0;
if(this.getPartId() != null) {
code += this.getPartId();
}
if(this.getPlayerId() != null) {
code += this.getPlayerId();
}
return code;
}
}
But instead of binding the parameters of the query this way:
Code:
select this_.part_id as part1_2_0_, this_.player_id as player2_2_0_, this_.timestamp as timestamp2_0_ from user_achievements.part_player this_ where (1=1) and (this_.part_id, this_.player_id) in ((partId1, playerId1), (partId2, playerId2))
it binds it this way:
Code:
select this_.part_id as part1_2_0_, this_.player_id as player2_2_0_, this_.timestamp as timestamp2_0_ from user_achievements.part_player this_ where (1=1) and (this_.part_id, this_.player_id) in ((partId1, partId2), (playerId1, playerId2))
The output from the jboss log looks like this:
Code:
16:48:24,882 INFO [STDOUT] Hibernate: select this_.part_id as part1_2_0_, this_.player_id as player2_2_0_, this_.timestamp as timestamp2_0_ from user_achievements.part_player this_ where (1=1) and (this_.part_id, this_.player_id) in ((?, ?), (?, ?))
16:48:24,882 TRACE [AbstractBatcher] preparing statement
16:48:24,884 TRACE [IntegerType] binding '5' to parameter: 1
16:48:24,884 TRACE [IntegerType] binding '11' to parameter: 2
16:48:24,884 TRACE [IntegerType] binding '123' to parameter: 3
16:48:24,884 TRACE [IntegerType] binding '123' to parameter: 4
but should be this:
Code:
16:48:24,882 INFO [STDOUT] Hibernate: select this_.part_id as part1_2_0_, this_.player_id as player2_2_0_, this_.timestamp as timestamp2_0_ from user_achievements.part_player this_ where (1=1) and (this_.part_id, this_.player_id) in ((?, ?), (?, ?))
16:48:24,882 TRACE [AbstractBatcher] preparing statement
16:48:24,884 TRACE [IntegerType] binding '5' to parameter: 1
16:48:24,884 TRACE [IntegerType] binding '123' to parameter: 2
16:48:24,884 TRACE [IntegerType] binding '11' to parameter: 3
16:48:24,884 TRACE [IntegerType] binding '123' to parameter: 4
Is this a bug in Hibernate or am I doing something really wrong?
Thanks in advance.
Jonas