I have two entities in in OneToMany Relationship:
The parent Entity:
#Entity
#Table(name = "PIANOTAGLIE")
public class PianoTaglia {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String pianoTaglia;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "pianoTaglia", cascade = CascadeType.ALL)
private List<Taglia> taglie;
public PianoTaglia() {
}
[...] Getter/Setter [...]
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.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;
PianoTaglia other = (PianoTaglia) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
And Child entity:
#Entity
#Table(name = "TAGLIE")
public class Taglia {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(length = 10, unique = true)
private String taglia;
#ManyToOne
private PianoTaglia pianoTaglia;
public Taglia() {
}
[...] Getter/Setter [...]
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.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;
Taglia other = (Taglia) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
For manage my Entities i use this generic Dao:
public abstract class JpaDAO<E> {
protected Class<E> entityClass;
#PersistenceContext(unitName = "PrudiPU")
protected EntityManager em;
#SuppressWarnings("unchecked")
public JpaDAO() {
ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
this.entityClass = (Class<E>) genericSuperclass.getActualTypeArguments()[0];
}
public List<E> findAll() {
TypedQuery<E> q = em.createQuery("SELECT h FROM " + entityClass.getName() + " h", entityClass);
return q.getResultList();
}
public void persist(E entity) {
em.persist(entity);
}
public E getReference(Long id) {
return em.getReference(entityClass, id);
}
}
Specialized for each class (this is PianoTagliaDao but TagliaDao is the same)
#Repository
public class PianoTaglieDao extends JpaDAO<PianoTaglia> {
}
When I create a PianoTaglia I keep a reference to the object with the generated ID... So i can navigate through my application and at any time i can create a Taglia. When i create a Taglia i use the reference to PianoTaglia, previusly created, in this way:
PianoTaglia pt = getPreviuslyCreatedPianoTaglia(); //this is an example
Taglia tg = new Taglia();
tg.setTaglia("XXL");
tg.setPianoTaglia(pt);
pt.getTaglie().add(tg);
taglieDao.persist(tg);
taglieDao.flush(); //i need to flush for keep generated ID
[...]
If i check the tables into DB is all ok! All the tables are well populated! But if i try to get all PianoTaglia the taglie collections are always empty:
List<PianoTaglia> pianoTagle = pianoTagliaDao.findAll();
for(PianoTaglia pt : pianoTaglie) {
assert pt.getTaglie().isEmpty();
}
after testing i've found the solution: when i create taglia i have to keep a new reference of PianoTaglie:
PianoTaglia old = getPreviuslyCreatedPianoTaglia();
PianoTaglia pt = pianoTaglieDao.getReference(old.getId()); //getReference call the namesake method in the EntityManager
Taglia tg = new Taglia();
tg.setTaglia("XXL");
tg.setPianoTaglia(pt);
pt.getTaglie().add(tg);
taglieDao.persist(tg);
taglieDao.flush(); //i need to flush for keep generated ID
[...]
In this way when i keep the PianoTaglia Objects the taglie collections are well Populated..
My question is: Why JPA have this behaviour?
It looks like you are storing the previously created PianoTaglia and keeping it well after it's context has closed, so that it is considered unmanaged by the persistence unit. Unmanaged entities are not tracked, so any changes made are not reflected in the database. This means that the pt.getTaglie().add(tg); code isn't done on something that the entityManager is aware of.
By using the getReference or find api, you are retrieving the managed instance of the entity, so that any changes made to it are tracked by the EntityManager. You could also have replaced the getReference and pianoTaglieDao.persist(tg); line with a pianoTaglieDao.merge(old) call which will merge changes made to the old PianoTaglia back into the persistence unit. It is probably better though to use getReference or find rather than cache the unmanaged entity to help reduce overwriting with stale data. Your cached object might not reflect the latest changes made, which might then be overwriten by the merge call, and for performance, it will allow you to expand your app later on to multiple threads and servers without having to make drastic changes.
hi I am not sure what fields do you have in the db but try to add
#JoinColumn("user_id")
for the next line
#ManyToOne
private PianoTaglia pianoTaglia;
result will be
#ManyToOne
#JoinColumn("pianoTagliaId")
private PianoTaglia pianoTaglia;
Related
I read an article about correct redefinition equals/hashCode:
https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
These overrides are performed in order not to lose the records already written to the Set.
Code:
#Entity
public class Client {
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
public Client() {
}
public Client(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Client client = (Client) o;
return Objects.equals(id, client.id) &&
Objects.equals(name, client.name);
}
public int hashCode() {
return 31;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("Client{");
sb.append("id=").append(id);
sb.append(", name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
}
Then I test my class to make sure that it works correctly:
#Transactional
public class ClientTest {
#PersistenceContext
protected EntityManager em;
#Test
public void storeToSetBeforeMerge_ShouldBeContains() {
Set<Client> map = new HashSet<>();
Client client1 = new Client("John");
Client client2 = new Client("Mike");
map.add(client1);
map.add(client2);
Client merge1 = em.merge(client1);
Client merge2 = em.merge(client2);
assertTrue(map.contains(merge1)); // not true!
assertTrue(map.contains(merge2)); // not true!
}
}
My question is why conditions are not met. After all, I have indicated that the hashCode returns the same value: 31.
What am I doing wrong?
I can not understand the meaning of this decision. If this solution does not solve the problem, I cannot find the element I need from the Set
You did not call persist() before merge() as it is done in article. Author of the article explains it in first comment.
Merge is for integrating changes on detached entities, which have been
persisted previously.
Lifecycle of a new entity begins with persist(). Then merge() is called on detached entity with ID, condition will be met.
It's because HashSet is not only comparing results of hashCode. What it does is the following:
It compares the results of hashCode and if the results are different, then it returns true.
If results of hashCode are same, then it compares objects using equals and returns the result.
It's because of performance - calculating hashCode is faster and it is advised for the hashCode not to produce collisions very often.
Edit
In your equals method you're comparing using id, which is wrong as id is generated by database:
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Client client = (Client) o;
return Objects.equals(id, client.id) && // <- comparison by id
Objects.equals(name, client.name);
}
In your test you're creating the objects without id and put them in HashSet, then you're generating id and checking the Collection again:
#Test
public void storeToSetBeforeMerge_ShouldBeContains() {
Set<Client> map = new HashSet<>();
Client client1 = new Client("John");
Client client2 = new Client("Mike");
map.add(client1); // <- those don't have an id
map.add(client2);
Client merge1 = em.merge(client1); // those do have an id
Client merge2 = em.merge(client2);
assertTrue(map.contains(merge1)); // whose with id are not in set
assertTrue(map.contains(merge2));
}
Please, consider the following example:
import org.hibernate.annotations.NaturalId;
import javax.persistence.Column;
import javax.persistence.Table;
#javax.persistence.Entity
#Table(name = "Entity", uniqueConstraints = {
#javax.persistence.UniqueConstraint(columnNames = {"firstNaturalId"}),
#javax.persistence.UniqueConstraint(columnNames = {"secondNaturalIdPart1", "secondNaturalIdPart2"})
})
class Entity {
#NaturalId
#Column(name = "firstNaturalId")
private String firstNaturalId;
#NaturalId
#Column(name = "secondNaturalIdPart1")
private String secondNaturalIdPart1;
#NaturalId
#Column(name = "secondNaturalIdPart2")
private String secondNaturalIdPart2;
// The remainder is omitted.
}
The desired functionality is to be able to retrieve an identified uniquely entity either by providing ('firstNaturalId') or the ('secondNaturalIdPart1', 'secondNaturalIdPart2') group.
Is it possible in Hibernate to have several natural identifiers combinations (groups) that uniquely identify an entity within a table?
A long time has passed. Maybe you've got your answer. I had this question in my mind. Found your question with no answer. Searched more. Found this Natural Id Mapping.
public class Entity{
#NaturalId #Embedded
private EntityNI entityNI;
public Entity(){}
// Setter and Getter is omitted
// .
// .
// .
#Override
public int hashCode(){
return (entityNI != null ? entityNI.hashCode() : 31);
}
#Override
public boolean equals(final Object object){
if(this == object){
return true;
}
if( !(object instanceof Entity) || (getClass() != object.getClass()) ){
return false;
}
return this.entityNI.equals( ((Entity)object).entityNI );
}
}
And the embedded natural ids:
#Embeddable
public class EntityNI{
#NotBlank #Basic(optional = false) #Column(name = "firstNaturalId")
private String firstNaturalId;
#NotBlank #Basic(optional = false) #Column(name = "secondNaturalIdPart1")
private String secondNaturalIdPart1;
#NotBlank #Basic(optional = false) #Column(name = "secondNaturalIdPart2")
private String secondNaturalIdPart2;
public EntityNI(){}
// Setters and Getters are omitted
// .
// .
// .
#Override
public int hashCode(){
return Objects.hash(firstNaturalId, secondNaturalIdPart1, secondNaturalIdPart2);
}
#Override
public boolean equals(Object object){
if(this == object){
return true;
}
if( !(object instanceof EntityNI) || (getClass() != object.getClass()) ){
return false;
}
final EntityNI other = (EntityNI) object;
return (this.firstNaturalId == other.firstNaturalId) && (this.secondNaturalIdPart1 == other.secondNaturalIdPart1) && (this.secondNaturalIdPart2 == other.secondNaturalIdPart2);
}
}
In a spring mvc app using hibernate and jpa, I recently switched to a composite primary key using an #Embeddable class. As a result, I need to update the JPA query that returns a given object based on its unique id. The following is the JPA code that used to work, but which no longer returns a result:
#SuppressWarnings("unchecked")
public Concept findConceptById(BigInteger id) {
Query query = this.em.createQuery("SELECT conc FROM Concept conc WHERE conc.id =:cid");
query.setParameter("cid", id);
return (Concept) query.getSingleResult();
}
How do I change the above query so that it returns the Concept with the most recent effectiveTime for the given id? Note that id and effectiveTime are the two properties of the ConceptPK composite primary key, and that thus the property definitions and getters and setters for id and effectiveTime are in the ConceptPK class and NOT in the Concept class.
The error thrown by the above is:
Caused by: java.lang.IllegalArgumentException:
Parameter value [786787679] did not match expected type [myapp.ConceptPK]
This is how the primary key is now defined in the Concept class:
private ConceptPK conceptPK;
And here is the code for the ConceptPK class:
#Embeddable
class ConceptPK implements Serializable {
#Column(name="id", nullable=false)
protected BigInteger id;
#Column(name="effectiveTime", nullable=false)
#Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
private DateTime effectiveTime;
public ConceptPK() {}
public ConceptPK(BigInteger bint, DateTime dt) {
this.id = bint;
this.effectiveTime = dt;
}
/** getters and setters **/
public DateTime getEffectiveTime(){return effectiveTime;}
public void setEffectiveTime(DateTime ad){effectiveTime=ad;}
public void setId(BigInteger id) {this.id = id;}
public BigInteger getId() {return id;}
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final ConceptPK other = (ConceptPK) obj;
if (effectiveTime == null) {
if (other.effectiveTime != null) return false;
} else if (!effectiveTime.equals(other.effectiveTime)) return false;
if (id == null) {
if (other.id != null) return false;
} else if (!id.equals(other.id)) return false;
return true;
}
#Override
public int hashCode() {
int hash = 3;
hash = 53 * hash + ((effectiveTime == null) ? 0 : effectiveTime.hashCode());
hash = 53 * hash + ((id == null) ? 0 : id.hashCode());
return hash;
}
}
To use parts of composite primary key in JPA query, you have to address them using its variable names:
public Concept findConceptById(BigInteger id) {
Query query = this.em.createQuery("SELECT conc FROM Concept conc WHERE conc.conceptPK.id =:cid order by conc.conceptPK.effectiveTime desc");
query.setParameter("cid", id);
return (Concept) query.getSingleResult();
}
I used Concept as entity name assuming the class with #Entity annotation is also named Concept.
This question contains information about similar problem, you may find it useful.
Please try this
#SuppressWarnings("unchecked")
public Concept findConceptById(BigInteger id) {
Query query = this.em.createQuery("from Concept conc WHERE conc.conceptPK.id = :cid order by conc.conceptPK.effectiveTime desc");
query.setParameter("cid", id);
return (Concept) query.getSingleResult();
}
Make sure conceptPK has getter and setter methods in Concept class.
I have a weird problem. I'm using play 2.1-SNAPSHOT with ebeans (=> mysql). I have a very small (test) setup and for some reason database updates and deletions don't work. Items are created in the DB... but updating them does not work.
Here's my bean (which extends a superclass that adds the timestamps (created and modified date)):
AbstractTimestamp (superclass):
#MappedSuperclass
public abstract class AbstractTimestampedBean extends AbstractIdentifiableBean {
/** The date this item has been created. */
#CreatedTimestamp
public Timestamp createdTime;
}
Project Bean (removed unimportant stuff) - hashCode and equals have been created by eclipse - here we overwrite the methods of play.db.ebean.Model:
#Entity
#Table(name = "Projects")
public class Project extends AbstractTimestampedBean {
private static final long serialVersionUID = -6160140283947231026L;
#NotNull
public String title;
#NotNull
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public User owner;
#NotNull
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public User creator;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public Set<User> participants;
#EnumMapping(nameValuePairs = "ACTIVE=A,INACTIVE=I,EXPIRED=E")
public enum Status {
ACTIVE, INACTIVE, EXPIRED
}
public Project() {
}
public Project(final String title, final User creator) {
this.title = title;
this.creator = creator;
this.owner = creator;
}
/*
* (non-Javadoc)
*
* #see play.db.ebean.Model#hashCode()
*/
#Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result
+ (this.creator == null ? 0 : this.creator.hashCode());
result = prime * result
+ (this.owner == null ? 0 : this.owner.hashCode());
result = prime * result
+ (this.participants == null ? 0 : this.participants
.hashCode());
result = prime * result
+ (this.title == null ? 0 : this.title.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* #see play.db.ebean.Model#equals(java.lang.Object)
*/
#Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
final Project other = (Project) obj;
if (this.creator == null) {
if (other.creator != null) {
return false;
}
} else if (!this.creator.equals(other.creator)) {
return false;
}
if (this.owner == null) {
if (other.owner != null) {
return false;
}
} else if (!this.owner.equals(other.owner)) {
return false;
}
if (this.participants == null) {
if (other.participants != null) {
return false;
}
} else if (!this.participants.equals(other.participants)) {
return false;
}
if (this.title == null) {
if (other.title != null) {
return false;
}
} else if (!this.title.equals(other.title)) {
return false;
}
return true;
}
Here's the very simple test case:
First run creates a projects - checks that it's there (nothing fails here)
Then we update some stuff - store it - and assert again... and here I can see that the db entries have not been updated.
http://pastebin.com/7zdzWGXw
Here's the superclass that we are using here:
public abstract class AbstractPersistableTestCase {
#Transactional
void saveBean(final Model bean) {
Ebean.save(bean);
}
#Transactional
void deleteBean(final Model bean) {
Ebean.delete(bean);
}
#Transactional
<T extends Model> void deleteBeans(final List<T> beans) {
Ebean.delete(beans);
}
}
Error message from jUnit4:
This is the assertion of the title in the update case => See: db entry has not been updated:
[error] Test test.models.ProjectTest.createAndUpdateProject failed: expected:<'Project_[NEW_]1350681993608'> but was:<Project_[]1350681993608'>
This happens when I try to delete the project:
[error] Test test.models.ProjectTest.deleteProjects failed: Data has changed. updated [0] rows sql[delete from user where id=? and name is null and email is null and created_time is null] bind[null]
Do you guys have an idea why this is happening? I'm really frustrated here...
Regards,
Sascha
It seems to me that you are not adding an Id to your classes.
Try to add this to your superclass:
#MappedSuperclass
public abstract class AbstractModel extends play.db.ebean.Model
{
#Id
public Long id;
public Long getId()
{
return id;
}
// ... here your other attributes
}
I'm currently using code like this to add a new entry to a set in my entity.
player = em.find(Player.class, playerId);
player.getAvatarAttributeOwnership().add(new AvatarAttributeOwnership(...));
It works, but every time I want to add one item, the whole set is loaded.
Is there a way (with a query maybe) to add the item without loading the rest? In SQL it would be something like INSERT INTO AvatarAttributeOwnership(player, data, ...) VALUES({player}, ...);
Currently uniqueness is maintained by the contract of Set and AvatarAttributeOwnership.equals, but I assume that won't work anymore. How can I enforce it anyway?
I'm using JPA2+Hibernate. Code:
#Entity
public class Player implements Serializable {
#Id
#GeneratedValue
private long id;
#ElementCollection(fetch=FetchType.LAZY)
// EDIT: answer to #2
#CollectionTable(uniqueConstraints=#UniqueConstraint(columnNames={"Player_id","gender","type","attrId"}))
Set<AvatarAttributeOwnership> ownedAvatarAttributes;
...
}
#Embeddable
public class AvatarAttributeOwnership implements Serializable {
#Column(nullable=false,length=6)
#Enumerated(EnumType.STRING)
private Gender gender;
#Column(nullable=false,length=20)
private String type;
#Column(nullable=false,length=50)
private String attrId;
#Column(nullable=false)
private Date since;
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
AvatarAttributeOwnership other = (AvatarAttributeOwnership) obj;
if (!attrId.equals(other.attrId)) return false;
if (gender != other.gender) return false;
if (!type.equals(other.type)) return false;
return true;
}
...
}
Try extra-lazy collections:
#LazyCollection(LazyCollectionOption.EXTRA)