i'm curious about how HQL would assert equality between an entity instances.
Let's say I have a Entity called Person
#Entity
public class Person{
#Id
private Long id;
private String name;
}
and Department
#Entity
public class Department {
#Id
private Long id;
#ManyToOne
private Person person;
}
then it's fine if I do the following statement:
Query query = getSession().createQuery("from Department d where d.person = ?");
query.setProperty(0,new Person(1L));
but, what if I have an Embedded entity and no pk defined? like
#Embeddable
public class Adress {
private String email;
private String street;
private Long identifier;
}
#Entity
public class Person{
#Id
private Long id;
private String name;
#Embedded
private Address address;
}
would have any way so I could tell JPA to make it work:
Query query = getSession().createQuery("from Person p where p.address = ?");
query.setProperty(0,new Address(1L));
even though it's not exactly a primary key?
For sure i know i'd work if I tried p.adress.identifier, and then passed just the Long value, but the point is, can I tell JPA provider how it's gonna kind of 'implement' equality my way?
Thank you all
No, it is not supported and it would be difficult in general or would not make sense in some situations, like when there are collections in the Embeddable.
If you find that you need this often though, consider converting such Embeddables to custom user types. Then you can perform comparisons the way you described.
Related
Let's say I have two entities:
#Entity
public class Phone {
#Id
private Long id;
private String number;
}
#Entity
public class Person {
#Id
private Long id;
private String name;
}
The relationship between a person and a phone is one to one.
How could I access only the phone's number in the Person entity mapped by the phone's id
#Entity
public class Person {
#Id
private Long id;
private String name;
// ???
private String phoneNumber;
}
The reason for not mapping the whole entity is because in some more realistic entities there are too many properties.
I don't think you can, but something like this might be acceptable:
public class Person {
#OneToOne
#JoinColumn(name = "phone_id")
private Phone phone;
public String getPhoneNumber() {
return phone.getNumber();
}
}
Although you have mapped the whole object, not just the single property, you have only exposed the single property you want. The other stuff is hidden.
Alternatively, do it at the DB layer using a View:
create view person_with_phone as
select p.id, p.name,f.number
from person p
join phone f on f.id=p.phone_id
and then have an entity class to match the view. You'll need to turn off schema creation in your JPA implementation.
I don't know exactly how to phrase this question unfortunately.
I have two tables that are only loosely related, PERSON and ASSIGNMENT.
public class Person {
#EmbeddedId
#Column
PersonPK id;
#Column
String otherStuff;
}
public class PersonPK {
#Column
long personNumber;
#Column
Date hireDate;
#Column
Date terminationDate;
}
public class Assignment {
#Id
#Column
long id;
#Column
long personId;
#Column
boolean active;
#Column
String otherStuff;
}
The PERSON one is easy to get with FROM Person p WHERE [current date in between hire and termination]
But i need to do a second query for assignments and I'd like to just pass the list of Persons back in instead of stripping out the id. So something like FROM Assignment a WHERE a.personId IN :persons.id.id AND a.active = true
Is this possible with a JPA query?
It seems a little curious to have an #EmbeddedId with date fields like that. To be honest I'd lose the PersonPK class and do something like this:
public class Person {
#Id
#Column
long personNumber;
#Column
Date hireDate;
#Column
Date terminationDate;
#Column
String otherStuff;
}
public class Assignment {
#Id
#Column
long id;
#ManyToOne
Person person;
#Column
boolean active;
#Column
String otherStuff;
}
Then you should be able to query by passing in the list of people:
List<Person> people = /* your list of people */
Query query = manager.createQuery("select a from Assignment a where a.person in :people");
query.setParameter("people", people);
List<Assigment> assignments = query.getResultList()
This works, because you are actually making a link between the Person and Assignment classes with the #ManyToOne annotation.
Edit: Seeing as you can't change the schema I'd just add a static helper to person to get the personNumbers for you:
public static List<Long> toPersonNumbers(List<Person> people) {
List<Long> numbers = new ArrayList<Long>();
for ( Person person: people ) {
numbers.add(person.id.personNumber);
}
return numbers;
}
Then you can just call that when you need to set the params:
List<Person> people = /* your list of people */
Query query = manager.createQuery("select a from Assignment a where a.personId in :people");
query.setParameter("people", toPersonNumbers(people));
List<Assigment> assignments = query.getResultList()
Assuming theses Entities
#Entity
public class EntityNote implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="SeqEntityNote", sequenceName="SeqEntityNote", allocationSize = 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SeqEntityNote")
private long id;
private Date date;
private String subject;
private String content;
#ManyToMany
private List<EntityTopic> listEntityTopic;
//setters/getters
#Entity
public class EntityTopic implements Serializable {
#Id
#SequenceGenerator(name="SeqEntityTopic", sequenceName="SeqEntityTopic", allocationSize = 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SeqEntityTopic")
private long id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
In my DB, a join table named "entity_note_list_entity_topic" records the ManyToMany relation.
This works correctly so far.
But I'd like to perform a count query like 'how many EntityNotes per EntitityTopic'
Unfortunatly I'm quite lost in this situation.
How this query can be written ?
Do I need other elements in my two entities ?
(In many examples I see a reverse relation using mappedBy attribute on ManyToMany.. Do I need this ?)
It will be the easiest if you make the many to many relation bidirectional. There are no serious extra costs involved, as it uses the same db structure, and the list are lazy loaded so if the relation is not being used the lists are not populated (you can hide the second direction by making accessors private).
Simply change:
#Entity
public class EntityTopic implements Serializable {
...
#ManyToMany(mappedBy="listEntityTopic")
private List<EntityNote> notes;
}
You can issue normal count jpql queries, for example:
SELECT count(n) from EntityTopic t INNER JOIN t.notes n where t.name =:name
so you don't neet to retrieve the notes and topics if don't need to.
But I also believe that your original mapping can also be queries with:
SELECT COUNT(n) FROM EntityNote n INNER JOIN n.listEntityTopic t WHERE t.name = :name
If you have the following code:
#Entity
public class EntityNote implements Serializable {
#ManyToMany(fetch = FetchType.LAZY)
private List<EntityTopic> topics;
}
#Entity
public class EntityTopic implements Serializable {
#ManyToMany(fetch = FetchType.LAZY)
private List<EntityNote> notes;
}
Then, topic.getNotes().size() will give you the number of notes associated with a topic. When using Hibernate as the JPA provider, a SELECT COUNT(...) query is issued for this instead of loading all the associated notes. If this does not work for you out-of-the-box, mark the collections as extra lazy using the instructions in this post.
I am trying to figure out the best way to accomplish a relationship in hibernate. I have a Customer object. Each customer has a technical contact, a billing contact, and a sales contact. Each type of contact has the exact same data structure (phone, email, address, etc).
My first thought was to create a Contact table, and then have three columns in the Customer table - sales_contact, billing_contact, technical_contact. That would make three distinct foreign key one-to-one relationships between the same two tables. However, I have found that this is very difficult to map in Hibernate, at least using annotations.
Another thought was to make it a many to many relationship, and have a type flag in the mapping table. So, any Customer can have multiple Contacts (though no more than three, in this case) and any Contact can belong to multiple Customers. I was not sure how to map that one either, though. Would tere be a type field on the map table? Would this attribute show up on the Contact java model object? Would the Customer model have a Set of Contact objects. or three different individual Contact objects?
So I am really looking for two things here - 1. What is the best way to implement this in the database, and 2. How do I make Hibernate map that using annotations?
It can be as simple as :
#Entity
public class Contact {
#Id
private String id;
private String phome;
private String email;
private String address;
// ... Getters and Setters
}
#Entity
public class Customer {
#Id
#GeneratedValue
private String id;
#ManyToOne
#JoinColumn(name = "ID")
private Contact billingContact;
#ManyToOne
#JoinColumn(name = "ID")
private Contact salesContact;
#ManyToOne
#JoinColumn(name = "ID")
private Contact technicalContact;
public Customer() {
}
// ... Getters and Setters
}
Now, if you want to make the difference between a BillingContact and a SalesContact at the object level, you can make Contact abstract, and implement it with each type of contact. You will have to annotate the parent class with #Inheritance to specify the inheritance strategy of your choice (SINGLE_TABLE sounds appropriate here, it will use a technical discriminator column - see http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/#d0e1168).
How about using #OneToOne and just naming the #JoinColumn differently for each type:
#Entity
public class Contact {
#Id
private String id;
private String phone;
private String email;
private String address;
// ... Getters and Setters
}
#Entity
public class Customer {
#Id
#GeneratedValue
private String id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="billingContact_ID")
private Contact billingContact;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="salesContact_ID")
private Contact salesContact;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="technicalContact_ID")
private Contact technicalContact;
public Customer() {
}
// ....
}
For each row in Customer table should create three rows in Contact table
I have the following Entities
#Entity
public class Conversation implements Serializable {
#Id
private int Id;
#Column
private Alias AliasA;
// SNIP
}
and
#Entity
public class Alias implements Serializable {
#Id
private String alias;
#Column
private String personalName;
#OneToMany(mappedBy = "alias", cascade = CascadeType.ALL)
#MapKeyColumn(name="address")
private Map<String, Recipient> recipients = new HashMap<String, Recipient>();
}
and
#Entity
public class Recipient implements Serializable {
#Id
#GeneratedValue
private long id;
#Column
private String address;
#Column
private RecipientStatus status;
#ManyToOne
private Alias alias;
}
And I would like to make something like the following JPQL query
SELECT conversation FROM Conversation conversation WHERE :sender MEMBER OF conversation.aliasA.recipients AND conversation.adId=:adID
Where :sender is in the key of my Map. The MEMBER OF keyword however only seems to work with Sets and not with Maps. I believe that JPA 2.0 should offer the KEY keyword, but this doesn't seem to be implemented in OpenJPA yet. Is there an alternative to this?
Update: Added information to clarify my question.
There is a VALUE keyword that should allow you to something like this:
SELECT c FROM Conversation c JOIN c.aliasA a JOIN a.recepients r
WHERE VALUE(r) = :sender AND conversation.adId=:adID
While axtavt's answer gave me the hint I needed, the answer was actually, that the error checking in IntelliJ 10.5.4 is not to be trusted in this case.
The KEY keyword does indeed work and the correct query was
SELECT conversation FROM Conversation conversation JOIN conversation.aliasA.recipients recipients WHERE KEY(recipients) = :senderAddress AND conversation.adId=:adID