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));
}
Related
JDK 17
SpringBoot latest
JPA latest
MySQL 8.0.31
I am trying to implement a strategy that makes sure that both the name and the email address of each user are unique.
User entity:
#Entity
public class User {
......
#EmbeddedId
protected UserId id;
......
}
User id:
#Embeddable
public class UserId implements Serializable {
#Serial
private static final long serialVersionUID = -622156255674132106L;
#Column(name = "name", nullable = false)
protected String name = "";
#Column(name = "email", nullable = false)
protected String email = "";
public UserId(String name, String email) {
setName(name);
setEmail(email);
}
public UserId() {}
public void setName(String name) {
this.name = name;
}
public String getName() {
return Objects.requireNonNullElse(name, "");
}
public void setEmail(String email) {
this.email = email;
}
public String getEmail() {
return Objects.requireNonNullElse(email, "");
}
}
Now, by default, it is marked as a conflict only if userA.name == userB.name && userA.email == userB.email, which means there can be two users having the same email address as long as they do not share one single name. How to stop this from happening? What I expect is userA.name == userB.name || userA.email == userB.email.
I've tried overriding equals() and hashcode() in the following way.
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof UserId userId)) return false;
if (Objects.equals(name, userId.name)) return true;
return Objects.equals(email, userId.email);
}
#Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (email != null ? email.hashCode() : 0);
return result;
}
However, it does not work. Also, breakpoints inside these two functions are not reached.
==========Edited==========
I've solved the original problem. But when it comes to UsersRepository.existsById(), it's still considered not to exist if either of the two columns does not match. How can I fix it?
Whether you do this via annotations and schema generation, or just by creating / modifying the schema directly, the answer is the same.
You will need to create a single unique constraint in the database naming both columns, not two separate constraints.
If you want a schema generation annotation to do this, supply the #UniqueConstraint annotation to the #Table annotation, e.g.
#Table(uniqueConstraints = {
#UniqueConstraint(columnNames = {
"name", "email"
})
})
public class UserId implements Serializable {
#Serial
private static final long serialVersionUID = -622156255674132106L;
#Column(name = "name", nullable = false, unique=true)
protected String name = "";
#Column(name = "email", nullable = false, unique=true)
protected String email = "";
This was an interview question and I was blank with no thoughts whatsoever.
Suppose I have an Employee class having three fields EmpId, EmpName, EmpAddress.
Now my job is to ensure that all objects that I create will be unique on the basis of EmpId such that when I try to create two objects with same EmpId, there has to be a mechanism to stop/alert me about it, maybe a compiler alert or any logic would do.
Only thing I could come up in the interview was to use Map (i.e HashMap) having key as EmpId to ensure uniqueness.
I know primary key in DB ensures this but how can I handle this in Java itself.
Any suggestions/thoughts on such line will be appreciated.
I can think of lots of ways. For example1:
Use a conventional Map that maps EmpId to Employee.
Use a Set of EmpIds.
Use a database table where each row represents an Employee and the EmpId is the primary key.
Use a sequence generator2 to generate a sequence of unique EmpId values, and don't allow the caller of the Employee object constructor to supply an EmpId.
If the EmpId is an integer and the space is dense, use a bitmap or BitSet rather than a Set of EmpIds.
If the EmpId is an integer and the space is really dense, you could use a TreeMap to represent ranges of EmpIds. (The logic is a bit complicated.)
1 - There are various other "poor" solutions that I won't enumerate.
2 - This is not really solving the problem as stated. However, this is possibly how you would implement this in practice; e.g. using an SQL SEQUENCE.
As other people have said, you can use a Map or a Set to hold you collection, and override the equals and hashCode methods to ensure proper use in collections.
If you wanted to check on creation of the object, you could e.g. use a Map and throw an exception:
import java.util.Set;
import java.util.HashSet;
public class Employee {
private static Map<EmpID, Employee> employees = new HashMap<EmpID, Employee>();
private EmpId empID;
private EmpName empName;
private EmpAddress address;
public Employee(EmpID empID, EmpName empName, EmpAddress empAddress) throws NotUniqueIDException {
if(!employees.keySet().contains(empID) {
this.empID = empID;
this.empName = empName;
this.empAddress = empAddress;
employees.put(empID, this);
} else {
// Error
throw NotUniqueIDException();
}
}
And then, when using:
try {
Employee employee = new Employee(empID, empName, empAddress);
} catch(NotUniqueIDException exception) {
// Do something here
}
As for if you needed to generate unique IDs, UUID.randomUUID() might be something to look at
I think that you can solve your problem using HashSet or another implementation of the Class Set and overriding the method equals to avoid duplicated entry of the class.
from the Java documentation of the class Set
Many methods in Collections Framework interfaces are defined in terms
of the equals method.
N.B. HashSet and Set implement the interface Collection
I will show you an example that avoid the duplicate using the method contains
from the Java documentation of the method contains of the class Set
boolean contains(Object o)
Returns true if this collection contains
the specified element. More formally, returns true if and only if this
collection contains at least one element e such that (o==null ?
e==null : o.equals(e)).
In this code I used a static HashSet to have an unique list of Employee. You can insert elements in the set only using the method addToSet that return true only if the you successfully insert the element in the HashSet.
import java.util.HashSet;
public class Employee {
private static HashSet<Employee> EmpSet= new HashSet<>();
private String EmpId, EmpName, EmpAddress;
public Employee(String id, String name, String addr){
EmpId = id;
EmpName = name;
EmpAddress = addr;
}
public static boolean addToSet(Employee employee){ //this can be synchronized if you need
if(EmpSet.contains(employee))
return false;
else
EmpSet.add(employee);
return true;
}
#Override
public boolean equals(Object to_compare){
if( !(to_compare instanceof Employee))
return false;
Employee other = (Employee) to_compare;
return this.EmpId.equals(other.EmpId);
}
}
The shown code works also if EmpId is Integer or Long.
If you are using this class from multiple threads you can make the method addToSet synchronized as suggested in the comments.
If you are using a DB this code is not valid, you can find specific methods for that specifica case.
Edit
I am sorry, actually you don't need the method addToSet because the method add of the class HashMap do exactly the same, so you can use its method or simply:
public static boolean addToSet(Employee employee){ //this can be synchronized if you need
EmpSet.add(employee);
}
Simple we need to override equals() method and golden rule says when ever you overrides equals() we must override hascode().
Here is working code for this
class Employee
{
private Integer id;
private String firstname;
private String lastName;
private String department;
Employee(Integer id, String firstname, String lastName, String department) {
this.id = id;
this.firstname = firstname;
this.lastName = lastName;
this.department = department;
}
public Integer getId() {
return id;
}
public String getFirstname() {
return firstname;
}
public String getLastName() {
return lastName;
}
public String getDepartment() {
return department;
}
public boolean equals(Object o) {
if(o == null)
{
return false;
}
if (o == this)
{
return true;
}
if (getClass() != o.getClass())
{
return false;
}
Employee e = (Employee) o;
return (this.getId() == e.getId());
}
public int hashCode()
{
final int PRIME = 31;
int result = 1;
result = PRIME * result + getId();
return result;
}
}
Driver Class
public class Test {
public static void main(String[] args) {
Employee e1 = new Employee(100,"Arpit","Agarwal","SDET");
Employee e2 = new Employee(101,"Neha","Agrawal","Web Developer");
Employee e3 = new Employee(103,"Priya","Gupta","Data Science");
Employee e4 = new Employee(100,"Mohit","Verma","Dev ops");
Set<Employee> mySet = new LinkedHashSet<>();
mySet.add(e1);
mySet.add(e2);
mySet.add(e3);
mySet.add(e4);
for(Employee obj : mySet) {
System.out.println(obj.getId() + ", " + obj.getFirstname() + ", " + obj.getLastName() + ", " + obj.getDepartment());
}
}
}
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 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;
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
}