Select from different join tables - java

I'm building a generic property system, where different entities may have properties (represented as 'PropertyEntity') assigned. Such a property entity has a key, a value and knows the id of the entity to which it is attached. Consider a 'UserEntity' in which a mapping for its properties would look like this:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
#JoinTable(name = "UserProperties",
joinColumns = #JoinColumn(name = "parentId"),
inverseJoinColumns = #JoinColumn(name = "id"))
private Map<String, PropertyEntity> properties;
There may also be more entities with properties, that each have their own join table.
I'm now wondering what would be the most efficient way to query for a User (or different entity that also has properties) with certain properties. Like 'Give me all Users where property A has value B and property X has value Y'.
Remember that the querying code must be generic in a way that it does not know whether a User or some other entity which has a property join table should be retrieved. The type to search for may be given as a Class object.
I'm currently running the naive approach in which the PropertyEntity also knows the name of the type to which it belongs. I then query the properties by their key and parent type and post process the result by manually finding the respective parent entities.
SELECT p FROM PropertyEntity p WHERE p.propertyKey in(?1) AND p.parentType = ?2
Where parameter 1 is a set of keys and parameter 2 the name of the parent type like 'com.domain.UserEntity'. This approach is obviously bad because it totally subverts the use of the extra join tables per entity and needs post filtering of the results by their values.

Related

Restrict mapping to get a specific one-to-one relationship for JPA-based sorting

Situation
I have two Hibernate entities, Document and TitleTranslation, with a one-to-many relationship, as there are multiple titles for a document, one for each language.
Document has a property id and TitleTranslation references that through document_id.
Therefore, to connect titles to documents, the natural solution would be a field as follows:
#JoinColumn(name = "document_id")
List<TitleTranslation> translations;
Goal
For sorting, I'm currently passing a Sort object to a JPA repository, e.g. repository.findAll(mySort).
The sort order property may reference transitive properties, such as owner.firstname (document has owner, owner has firstname).
This works well as long as transitive properties are joined in a one-to-one fashion, but breaks down for one-to-many relationships as above:
I'd like to sort by a document's title, but there's just a list of translations.
However, since I'm just interested in one specific language, there is just one title in the list relevant to me, therefore a de-facto one-to-one relationship.
How can I achieve a one-to-one mapping to a translation of a certain language, so I could sort by e.g. translationEn.title?
Partial solution
As it turns out, mapping through OneToOne results in one of the translations being picked (the first one?):
#OneToOne(mappedBy = "document")
TitleTranslation translation;
This relies on a corresponding mapping in TitleTranslation:
#JoinColumn(name = "document_id", referencedColumnName = "id")
Document document;
Now, I would like to indicate on the translation property that only translations with language='en' should be joined.
I tried to use the Where annotation, but this only works in connection with one-to-many relationships.
Thus, the following doesn't work as expected:
#OneToOne(mappedBy = "document")
#Where(clause = "language='en'")
TitleTranslation translation;
You could try using filters:
1) Define the filter:
#FilterDef(name = "enFilter"
, defaultCondition=" language='en'")
public class TitleTranslation {
2) Use the filter:
#ManyToOne(mappedBy = "document")
#Filter(name = "enFilter")
TitleTranslation translation;
You may need to define a #ManyToOne instead of #OneToOne though.
3) In transactional method:
Session session = sessionFactory.getCurrentSession();
session.enableFilter("enFilter");

How to replace a #JoinColumn with a hardcoded value?

For more context, please see my other question.
I want to join my Employee entity class to Code entity class but the Code PK is composite (DOMAIN, CODE) because it lists many different code domains, yet the codes that go in the Employee class/table are all from within a single domain, eliminating the need to have a domain field in the Employee class/table (because it would always be the same).
Can I join Employee to Code by using the CODE field in the Employee table and a hardcoded value (e.g. EMP_TYPE) instead of a redundant column?
If my Employee class/table did indeed have that redundant column, I would join it like this:
#JoinColumns({
#JoinColumn(name = "EMP_TYPE_CODE", referencedColumnName = "CODE"),
#JoinColumn(name = "DOMAIN", referencedColumnName = "DOMAIN)})
#ManyToOne(optional = false)
private Code sharedStatute;
but I REALLY don't want to have that extra column in the DB and in the class because it will always be the same.
What I am trying to accomplish would be equivalent to the following in SQL:
SELECT e.emp_id, e.first_name, e.last_name, c.description as emp_type
FROM Employee e JOIN Code c
ON e.emp_type_code = c.code
WHERE c.domain = 'EMP_TYPE'
as opposed to adding a field domain in the Employee table and populating EVERY SINGLE RECORD with the same value ('EMP_TYPE') and then doing:
SELECT e.emp_id, e.first_name, e.last_name, c.description as emp_type
FROM Employee e JOIN Code c
ON e.emp_type_code = c.code
AND e.domain = c.domain
The former is more efficient because it saves me from having to have a redundant field. So what I am trying to do is the same thing but in JPA.
Some of you may say something to the effect of "why not have a separate lookup table for each code" but I think that's a terrible idea and causes cluttering of DB tables and corresponding application entities. It is much better to have a single code lookup table partitioned by code type (or domain).
This should work?
#JoinColumn(name = "EMP_TYPE_CODE", referencedColumnName = "CODE")
#Where(clause = "domain = 'EMP_TYPE'")
#ManyToOne(optional = false)
private Code sharedStatute;
Note: Apparantly this solution is OpenJPA only.
There is a way to do this with javax.persistence annotations only:
#JoinColumns({
#JoinColumn(name = "EMP_TYPE_CODE", referencedColumnName = "CODE"),
#JoinColumn(name = "CODE_TABLE_NAME.DOMAIN", referencedColumnName = "'EMP_TYPE'")})
#ManyToOne(optional = false)
private Code sharedStatute;
Note the single quotes in the referencedColumnName, this is the String value you are looking for.
There is a downside however, when you have multiple Code objects in your entity, the join on DOMAIN will be done only once in jpa, giving bad results. At the moment I circumvent this by making those field load lazily, but that's not ideal.
More information here.

Hibernate embeddable list mapping with identifier

I have a Person entity with an embeddable Address and there's a one-to-many relation between them (a person can have multiple addresses). The current mapping is something like this:
#Embeddable
public class Address {
// ... attributes
}
#Entity
public class Person {
#ElementCollection(fetch = FetchType.EAGER)
#JoinTable(name = "Person_addresses", joinColumns = #JoinColumn(name = "personid")
)
/*
Attribute ovverrides with annotations
*/
private java.util.Set<Address> addresses = new java.util.HashSet<Address>();
}
Using this annotation means that in the database I have a Person_addresses table which contains all the address attributes and a personid. But it also means that if I have a person with an address list and I update the address list, Hibernate deletes all the related records and inserts them (the modified ones) again.
As far as I know there's a way to have a primary key in this table for each record - in this case hibernate can decide which item of the list needs to be updated. So my question is, how can I map an embeddable list with identifiers in the joining table? (I hope it's understandable what I want:)).
http://en.wikibooks.org/wiki/Java_Persistence/ElementCollection#Primary_keys_in_CollectionTable
The JPA 2.0 specification does not provide a way to define the Id in
the Embeddable. However, to delete or update a element of the
ElementCollection mapping, some unique key is normally required.
Otherwise, on every update the JPA provider would need to delete
everything from the CollectionTable for the Entity, and then insert
the values back. So, the JPA provider will most likely assume that the
combination of all of the fields in the Embeddable are unique, in
combination with the foreign key (JoinColumn(s)). This however could
be inefficient, or just not feasible if the Embeddable is big, or
complex. Some JPA providers may allow the Id to be specified in the
Embeddable, to resolve this issue. Note in this case the Id only needs
to be unique for the collection, not the table, as the foreign key is
included. Some may also allow the unique option on the CollectionTable
to be used for this. Otherwise, if your Embeddable is complex, you may
consider making it an Entity and use a OneToMany instead.
So thats it, it can't be done.
As maestro's reply implies, the only portable solution is to convert this to use an entity and a one-to-many.
That said, Hibernate has a non-spec feature called an "id bag" which allows you to map a basic or embeddable collection with an identifier for each row, thereby giving you the efficient updates you want:
#Entity
public class Person {
#CollectionId( columns={"address_id"}, type="int", generator="increment" )
#ElementCollection(fetch = FetchType.EAGER)
#JoinTable(name = "Person_addresses", joinColumns = #JoinColumn(name = "personid"))
private java.util.List<Address> addresses = new java.util.ArrayList<Address>();
}
Notice the switch from Set to List however. Also notice the generated table structure... looks an awful lot like an entity ;)

org.hibernate.AnnotationException: referencedColumnNames referencing not mapped to a single property

I ran into the below exception while mapping a one-to-one between 2 entities. The 1 first entity has embedded composite key. The second entity also has embedded composite key. The tables are part of legacy system. Data is flat, relations are not well defined. Please help.
Caused by: org.hibernate.AnnotationException: referencedColumnNames(FLAG_NAME) of net.javabeat.spring.model.ReferralsM.mnEditFlag referencing net.javabeat.spring.model.MnEditFlag not mapped to a single property
at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:205)
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:116)
at org.hibernate.cfg.Configuration.processEndOfQueue(Configuration.java:1515)
at org.hibernate.cfg.Configuration.processFkSecondPassInOrder(Configuration.java:1440)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1358)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1727)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1778)
at org.springframework.orm.hibernate4.LocalSessionFactoryBuilder.buildSessionFactory(LocalSessionFactoryBuilder.java:247)
at org.springframework.orm.hibernate4.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:373)
at org.springframework.orm.hibernate4.LocalSessionFactoryBean.afterPropertiesSet(LocalSessionFactoryBean.java:358)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1571)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1509)
... 34 more
Here is my one to one mapping in the main/parent table.
#OneToOne(targetEntity = MnEditFlag.class, fetch = FetchType.LAZY)
#JoinColumn(name = "REFFLG", referencedColumnName = "FLAG_NAME", insertable = false, updatable = false)
MnEditFlag mnEditFlag;
The cause of the issue is that you are trying to use a single join column, while the identity of the referenced entity is defined by multiple columns. You simply have to define all the needed join colums and you are good to go:
#JoinColumns({
#JoinColumn(name = "REFFLG", referencedColumnName = "FLAG_NAME"),
#JoinColumn(name = "OTHER_KEY", referencedColumnName = "SOME_OTHER_NAME"))
...
})
MnEditFlag mnEditFlag;
OT: you should not need the targetEntity attribute on the OneToOne annotation. This is already defined by the type of the target entity: MnEditFlag. You probably need targetEntity only for untyped Collections.
EDIT: If there is a single join column, which is only part of the PK and you cannot change the existing tables, perhaps you can define a new join table with all necessary columns.
Then you define the join table to be used for the relationship:
#JoinTable(name="ReferralsM_MnEditFlag",
joinColumns={
#JoinColumn(name="REFERRALS_ID1", referencedColumnName="ID1"),
#JoinColumn(name="REFERRALS_ID2", referencedColumnName="ID2")
}, inverseJoinColumns={
#JoinColumn(name="REFFLG", referencedColumnName="FLAG_NAME"),
#JoinColumn(name="REFFLG2", referencedColumnName="FLAG_NAME2")
})
MnEditFlag mnEditFlag;
You would have to migrate the data to the new join table programmatically or by queries.
Unfortunately you cannot define a relationship with a partial PK with vanilla JPA, perhaps Hibernate has such a feature, like one-to-one by query, but I cannot confirm it.
EDIT2: The join table should contain all PK columns for both entities to be fully functional. That is why I have defined two join columns for each side in my example. The number of columns and their names are purely exemplary.
Extracting only the one join column you already have in your table would not add any value.
The optimal solution would be to change the entity tables so they define a proper relationship between the entities. It is sufficient to change only one of the tables and define it as the owning side as you did, but with all FK columns. This would require a migration effort, since you would need to add the data for the missing FK columns like described above.
EDIT3: The strategies I recommended were based on the assubmption that you want to have complete CRUD functionality. If you just want to pull the data for display or reporting, a view is perfectly fine. You can define the columns you need and map the whole view to a single entity. However, as it is a view, you will not be able to change the data or migrate it.
You could use targetEntity on #ManyToOne. The targetEntity should be equal to the parent (probably abstract) class.
The temporary solution is here depending to your class hierarchy:
https://hibernate.atlassian.net/browse/HHH-4975

How can I access the underlying column after defining a #ManyToOne relationship on it in Spring?

I'm using Spring 3.2 with Roo 1.2.3 to build a database-backed Java application via Hibernate. I have several bidirectional OneToMany/ManyToOne relationships among the tables in my database. When I set up the ManyToOne side of the relationship using #JoinColumn (via "field reference" in Roo), a new field whose type is the related entity (the "one" in ManyToOne) is created. However, once this is done, there seems to be no way to access the underlying column value on which the ManyToOne relationship is based. This is a problem when the underlying join column contains data needed by the application (i.e. when the join column contains product stock numbers).
Is there any way to set up my entity class so that the column on which its ManyToOne relationship is based remains accessible without traversing the new join property? How can I define an accessor method for the value of this column?
I've been looking online for an answer to this question for several days, but to no avail. Thanks in advance for your help.
just map the column a second time with insertable=false and updateable=false
To make it more concrete. It's possible to do a HQL-SELCT and restrict a ManyToOne relationship, without any join in the resulting SQL:
Instead of using a join in
session.createQuery("FROM Person person WHERE person.adress.id = 42")
we use can use the adress_idcolumn
session.createQuery("FROM Person person WHERE person.adressId = 42")
This works, if you specify an additional adressId field, which is only used as mapping info for Hibernate:
#Entity
#Access(AccessType.FIELD)
public class Person{
#Id
String id;
#JoinColumn(name = "adress_id")
#ManyToOne(fetch = FetchType.LAZY)
#Nullable
public Adress adress;
#Column(name = "adress_id", insertable = false, updatable = false)
private String adressId;
}
#Entity
#Access(FIELD)
public class Adress{
#Id
String id;
}
The AccessType.FIELD is not needed (But we can leave getters/setters in example). The FetchType.LAZY and #Nullable are also optional, but make it clear when it makes sense to use it. We are able to load Person entities which have a specific Address (we know the address id). But we don't need a join because it's not needed for the WHERE-clause and not for the initial fetch (the address can be fetched lazy).

Categories