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.
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));
}
A multi-tiered application that I am developing accesses its database through a Payara application server. Originally I was using webservices (jax-ws) to provide the access. That decision was prompted by the ease of creating the services by using the #WebService and #Stateless annotations on my entity facade classes.
Due to some limitations of webservices (things like equals and hashCode methods not being created in the webservice interface), I decided to try to use EJB's to accomplish the same functionality. Using the webservices I was able to successfully perform all CRUD functionality on all of the database entities. All of my entity classes extend an AbstractEntity class, which is annotated #MappedSuperClass.
#MappedSuperclass
public abstract class AbstractEntity {
#Column(name = "UUID")
#Basic
private String uuid;
#Version
#Access(AccessType.FIELD)
#Column(name = "Revision")
#Basic
private long revision;
public AbstractEntity() {
uuid = UUID.randomUUID().toString();
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
/**
* #return entity's database primary key if persisted
*/
public abstract Long getId();
public long getRevision() {
return revision;
}
public void setRevision(long revision) {
this.revision = revision;
}
/**
* #return true if this entity is persisted to database, false
otherwise.
*/
public boolean isPersisted() {
return getId() != null;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof AbstractEntity) {
return this.uuid.equals(((AbstractEntity) obj).uuid);
}
return false;
}
#Override
public int hashCode() {
return uuid.hashCode();
}
}
The client application correctly looks up the remote interfaces through JNDI, and I'm able to run my methods to query the data and return result lists exactly the same as I can using webservices. The problem, however, is in the version number and uuid that are returned with each entity instance. In all cases, the revision number that is returned is 0, and the revision and uuid don't match the revision and uuid stored in the database. I have verified that the result of my query on the server contains entities that have the correct version numbers, but when the entities get to the client, all of them are set to 0. Of course, if I make any changes to the entity on the client and then try to update the entity, I get an optimistic locking exception on the update method.
Does this have something to do with the entities being detached from the database? The update method is:
#Override
public ShiplistItem updateShipList(ShiplistItem shipitem) {
LOGGER.log(Level.INFO, "Entering updateShipList.");
LOGGER.log(Level.INFO, "shipitem: {0}", shipitem.toString());
if (shipitem.isPersisted()) {
return em.merge(shipitem);
} else {
em.persist(shipitem);
return shipitem;
}
}
I don't understand why the webservice would return the entities correctly, but the ejb seems to neglect the stored values for revision and uuid.
All help is appreciated!!
EDIT - Entity class snippet
public class ShiplistItem extends AbstractEntity implements
Serializable, Comparable<ShiplistItem> {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, nullable = false)
private Long id;
....
EDIT #2
#Override
public List<ShiplistItem> listShipListByDate(String date) {
Query query = em.createQuery("SELECT s from ShiplistItem s
where s.shipdate = :shipdate", ShiplistItem.class)
.setParameter("shipdate", date);
LOGGER.log(Level.INFO, "SQL: {0}.", query.toString());
List<ShiplistItem> result = new ArrayList<>();
try {
result = (List<ShiplistItem>) query.getResultList();
} catch (Exception ex) {
LOGGER.log(Level.INFO, "No shipping list for date {0}", date);
}
for (ShiplistItem shiplistItem : result) {
LOGGER.log(Level.INFO, "revision: {0}",shiplistItem.getRevision());
}
LOGGER.log(Level.INFO, "shiplist size: {0}", result.size());
return result;
}
I am new JPA and I am facing exceptions when I retrieve the values from composite primiary key table.
Exception Description:
Problem compiling [select t from ASSIGN_TASK_EMPLOYEE t].
[14, 34] The abstract schema type 'ASSIGN_TASK_EMPLOYEE' is unknown.
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1605)
Following are my code
#Entity
#Table(name = "ASSIGN_TASK_EMPLOYEE")
//#IdClass(AssignTaskEmployeePk.class)
public class AssignTaskEmployee implements Serializable {
#EmbeddedId
private AssignTaskEmployeePk assignTaskEmployeePk;
public AssignTaskEmployeePk getAssignTaskEmployeePk() {
return assignTaskEmployeePk;
}
public void setAssignTaskEmployeePk(AssignTaskEmployeePk assignTaskEmployeePk) {
this.assignTaskEmployeePk = assignTaskEmployeePk;
}
}
#Embeddable
public class AssignTaskEmployeePk {
private String employeeId;
private String taskId;
public AssignTaskEmployeePk() {
}
#Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
if (obj instanceof AssignTaskEmployeePk) {
AssignTaskEmployeePk employeePk = (AssignTaskEmployeePk) obj;
if (!employeePk.getEmployeeId().equals(this.employeeId)) {
return false;
}
else if (!employeePk.getTaskId().equals(this.taskId)) {
return false;
}
}
else {
return false;
}
return false;
}
#Override
public int hashCode() {
return employeeId.hashCode() + taskId.hashCode() ;
}
public String getEmployeeId() {
return employeeId;
}
public void setEmployeeId(String employeeId) {
this.employeeId = employeeId;
}
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
}
I have added four values in the databases for composite primary key ASSIGN_TASK_EMPLOYEE (table) which PK table
EMP_ID TASKID
1 2
2 4
3 5
4 6
Now I would like to get the tasks assigned to emp_id 1
For that I wrote the query below:This would supposed to return list of AssignTaskEmployee object.
entityManager.createQuery("select t from ASSIGN_TASK_EMPLOYEE t").getResultList()
When I execute this query, I am getting the following exception
Exception Description:
Problem compiling [select t from ASSIGN_TASK_EMPLOYEE t].
[14, 34] The abstract schema type 'ASSIGN_TASK_EMPLOYEE' is unknown.
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1605)
JPQL should use entities names, default are the name of the class. AssignTaskEmployee
It should be
entityManager.createQuery("select t from AssignTaskEmployee t").getResultList()
The above will be return all the records in table ASSIGN_TASK_EMPLOYEE.
If you want to retrieve a specific record with JPQL you should use WHERE statement as it follows:
Query query = entityManager.createQuery("select t from AssignTaskEmployee t WHERE
t.assignTaskEmployeePk.employeeId = :employeeId and t.assignTaskEmployeePk.taskId = :taskId")
query.setParameter("employeeId", 1);
query.setParameter("taskId",1);
query.getSingleResult() //As expected to have only one record.
Read this to query over EmbeddedId
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
}