I'm very new to JPA (based on eclipselink) so be patient with me and my question. I found JPA is an elegant way to handle simple DB statements. But, now I'd like to do some more complicated task.
So let me explain what I want: I've got a table holding person data (lets say the persons name). In an other table I'd like to keep address lists. Because a person way part of several adresslists and one adress list may keep several persons I need an ref-table jo join these collections.
So my DB-tables look like this:
SQL (not exact DDL syntax but it helps to understand!) --without constraints etc
--the person keeps data of persons
CREATE TABLE a_person
(
oid serial PrimaryKey NOT NULL,
vorname character varying(50),
nachname character varying(50) NOT NULL
--
)
--the adresslist is quite simple: just a name of the list is storred
CREATE TABLE a_adressliste (
(
oid serial PrimaryKey NOT NULL,
label character varying(25) NOT NULL
--<more collumns here>
)
--the table refering the list the the containing persons
CREATE TABLE a_listen_person
(
fk_adressliste bigint PrimaryKey NOT NULL, --ref to a_person.oid
fk_person bigint PrimaryKey NOT NULL, --ref to a_adressliste.oid
)
Having this structure and some data in the DB it's easy to select the adress lists using the following SQL statement:
select * from a_person p where p.oid in (
select
lp.fk_person
from
a_listen_person lp, a_adresssliste al
where
lp.fk_adressliste = al.oid AND al.label = {LISTE_LABEL}
)
Now, According tho the structure within the DB, I've got the corresponding JAVA POJO's to these tables (I've skipped the annotations to keep my code a little shorter)
JAVA
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "oid")
private Integer oid;
private String vornmae;
private String nachname;
//getter; setter and the other missing stuff...
}
public class Adressliste implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "oid")
private Integer oid;
private String label;
}
public class ListenPerson implements Serializable {
#EmbeddedId
private ListenPersonPK listenPerson;//build from Person.oid and Adressliste.oid
private Person person;
private Adressliste adresssliste;
//getter; setter and the other missing stuff...
}
Now I've written a finder method in JAVA where I use a CriteriaBuilder to filter the entities by several attributes (according to the Person POJO). But I did not manage to select the Person according to a given list name.
By now my method looks like this:
public TypedQuery<Person> prepareQueryFiltered(Filter filter) {
TypedQuery<Person> retVal;
EntityManager em = this.getEntityManager();
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Person> query = builder.createQuery(Person.class);
Root<Person> cust = query.from(Person.class);
query.select(cust);
List<Predicate> predicateList = new ArrayList<Predicate>();
Predicate predicate;
if (null != filter.getVorname()) {
predicate = builder.like(
builder.lower(cust.<String>get("vorname")),
"%" + filter.getVorname().toLowerCase() + "%");
predicateList.add(predicate);
}
if (null != filter.getNachname()) {
predicate = builder.like(
builder.lower(cust.<String>get("nachname")),
"%" + filter.getNachname().toLowerCase() + "%");
predicateList.add(predicate);
}
//some more filtered attributes ...
query.where(predicateList.toArray(new Predicate[predicateList.size()]));
retVal = em.createQuery(query);
return retVal;
}
As you can imagine the filter attribute keeps all the data to filter my entities with. But how does the code look like if I'd like to get all the person entities belonging to a given list name?
I started to use 'Subquery's but I did not get the correct syntax. Could you please give me some hints?
For examples of sub-queries in the Criteria API see,
http://en.wikibooks.org/wiki/Java_Persistence/Criteria#Subquery
Also, I don't think you need a sub-query, a simple join should work.
Select p from Person p, ListenPerson l where l.person = p and l.adresssliste.label = :label
Related
Executing the following JPA query while using EclipseLink v2.7.4:
SELECT pr FROM AbstractProduct pr WHERE pr.shelve.superMarket.id = :1 ORDER BY pr.sortOrder
Gives the following error:
Unknown column 't0.SORTORDER' in 'order clause'
Error Code: 1054
Call: SELECT t2.ID, t2.SORTORDER, t2.SHELVE_ID FROM APPLE t2, SHELVE t1 WHERE ((t1.SUPERMARKET_ID = ?) AND (t1.ID = t2.SHELVE_ID)) ORDER BY t0.SORTORDER
bind => [12]
The query is refering to t0 but nowhere in the generated query does it define which table t0 is.
These are the entities that I'm using:
#Entity
public class SuperMarket {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToMany(mappedBy = "superMarket")
List<Shelve> shelves;
}
#Entity
public class Shelve {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
protected SuperMarket superMarket;
#OneToMany(mappedBy = "shelve")
protected List<AbstractProduct> products;
}
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractProduct {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
protected Shelve shelve;
protected long sortOrder;
}
#Entity
public class Apple extends AbstractProduct {
}
#Entity
public class Banana extends AbstractProduct {
}
Which results in the following queries:
CREATE TABLE SUPERMARKET (ID BIGINT NOT NULL, PRIMARY KEY (ID));
CREATE TABLE SHELVE (ID BIGINT NOT NULL, SUPERMARKET_ID BIGINT, PRIMARY KEY (ID));
CREATE TABLE APPLE (ID BIGINT NOT NULL, SORTORDER BIGINT, SHELVE_ID BIGINT, PRIMARY KEY (ID));
CREATE TABLE BANANA (ID BIGINT NOT NULL, SORTORDER BIGINT, SHELVE_ID BIGINT, PRIMARY KEY (ID));
CREATE TABLE ABSTRACTPRODUCT (SHELVE_ID BIGINT);
The last table ABSTRACTPRODUCT should not be created since it is an Abstract Java Entity and I'm using the table per class inheritance style. This seems to be a bug in eclipselink it is also discussed in this question: Understanding of TABLE_PER_CLASS with keys in eclipselink It is the combination of the inheritance with the OneToMany relations that seems to trigger the create table statement. Not sure if this bug is related to the query error that I mentioned at the start. I think not as this table doesn't even have the sort order field.
When I remove the ORDER BY clause the query will execute succesfully. When I change the query to only go one level up it will also execute succesfully:
SELECT pr FROM AbstractProduct pr WHERE pr.shelve.id = :1 ORDER BY pr.sortOrder
For a test I got rid of the inheritance and let the Shelve entity have a OneToMany relation to Apple directly, where Apple did not extend any other class, in that case the query is also executed succesfully. But I need the abstract class and inheritance.
Any idea why the generated query is wrong in this case?
As is noted in an answer below I could use a different inheritance strategy to solve this problem in a different way. I choose the table per class type because that allows me to use the abstract entity in queries and the concrete classes get a table with all the fields in it. I was hoping that this helps with performance when doing a lot of inserts and selects from the concrete classes because that would only involve a single db table.
Update
I think this is an error in EclipseLink I have created two bugreports:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=549866 for the abstract class creating a table
https://bugs.eclipse.org/bugs/show_bug.cgi?id=549868 for the error in the query
I was able to reproduce your problem with the inheritance strategy TABLE_PER_CLASS. The query executed as expected once I changed it to InheritanceType.JOINED and recreated the schema of the database (in my case: PostgreSQL 10.9).
So the code should be changed to:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class AbstractProduct {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne
protected Shelve shelve;
protected long sortOrder;
}
Note well:
I added the missing #ManyToOne annotation to the attribute shelve.
#ManyToOne should also be added to protected SuperMarket superMarket; in the entity Shelve.
The resulting DB schema looks as follows:
CREATE TABLE public.abstractproduct
(
id bigint NOT NULL,
dtype character varying(31) COLLATE pg_catalog."default",
sortorder bigint,
shelve_id bigint,
CONSTRAINT abstractproduct_pkey PRIMARY KEY (id),
-- FK definitions left out for brevity
)
And Apple, for instance, becomes:
CREATE TABLE public.apple
(
id bigint NOT NULL,
CONSTRAINT apple_pkey PRIMARY KEY (id),
-- FK definitions left out for brevity
)
Hope it helps.
I have an Entity with a ManyToOne Relationship to the Primary Key of another entity. When I create a query that references this Foreign Key eclipseLink always creates a join instead of simply accessing the Foreign Key.
I have created a highly simplified example to show my issue:
#Entity
public class House {
#Id
#Column(name = "H_ID")
private long id;
#Column(name = "NAME")
private String name;
#ManyToOne
#JoinColumn(name = "G_ID")
private Garage garage;
}
#Entity
public class Garage{
#Id
#Column(name = "G_ID")
private long id;
#Column(name = "SPACE")
private Integer space;
}
I created a query that should return all houses that either have no garage or have a garage with G_ID = 0 using the CriteriaBuilder.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<House> query = cb.createQuery(House.class);
Root<House> houseRoot = query.from(House.class);
Path<Long> garageId = houseRoot.get(House_.garage).get(Garage_.id);
query.where(cb.or(cb.equal(garageId , 0), cb.isNull(garageId)));
TypedQuery<House> typedQuery = entityManager.createQuery(query);
List<House> houses = typedQuery.getResultList();
The generated query is:
SELECT h.NAME, h.G_ID FROM HOUSE h, GARAGE g WHERE (((h.G_ID= 0) OR (g.G_ID IS NULL)) AND (g.G_ID = h.G_ID));
I don't understand why
The or condition first references table HOUSE and then GARAGE (instead of HOUSE)
The join is created in the first place.
The correct query should look like this in my understanding:
SELECT h.NAME, h.G_ID FROM HOUSE h WHERE (((h.G_ID= 0) OR (h.G_ID IS NULL));
Or if a join is made it should take into account that the ManyToOne relationship is nullable and therefore do a LEFT OUTER JOIN.
SELECT h.NAME, h.G_ID FROM HOUSE h LEFT OUTER JOIN GARAGE g ON (h.G_ID = g.G_ID ) WHERE (h.G_ID = 0) OR (g.G_ID IS NULL);
(Note both these queries would work correctly in my more complicated setup. I also get the same error when only wanting to retrieve all houses that have no garage.)
How can I achieve this (while still using the CriteriaBuilder and ideally not having to change the DB Model)?
(Please let me know any additional information that might be required, I'm very new to this topic and came across this issue while migrating an existing application.)
-- edit --
I have found a solution to my problem that will result in slightly different behaviour (but in my application that part of the code I had to migrate didn't make much sense in the first place). Instead of using
Path<Long> garageId = houseRoot.get(House_.garage).get(Garage_.id);
I use
Path<Garage> garage = houseRoot.get(House_.garage);
And then as expected table Garage isn't joined anymore. (I assume the code previously must have been some kind of hack to get the desired behaviour from openJPA)
I don't understand why
The or condition first references table HOUSE and then GARAGE (instead of HOUSE)
I believe this is implementation specific; in any case, it shouldn't have any bearing on the results.
The join is created in the first place.
By saying Path<Long> garageId = houseRoot.get(House_.garage).get(Garage_.id) you're basically telling EclipseLink: 'join Garage to House, we're gonna need it'. That you then access Garage_.id (and not, for example, Garage_.space) is inconsequential.
If you don't want the join, simply map the G_ID column one more time as a simple property: #Column(name = "G_ID", insertable = false, updatable = false) private Long garageId. Then refer to House_.garageId in your query.
Or if a join is made it should take into account that the ManyToOne relationship is nullable and therefore do a LEFT OUTER JOIN.
Path.get(...) always defaults to an INNER JOIN. If you want a different join type, use Root.join(..., JoinType.LEFT), i. e. houseRoot.join(House_.garage, JoinType.LEFT).get(Garage_.id).
One solution that results in the same behaviour is:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<House> query = cb.createQuery(House.class);
Root<House> houseRoot = query.from(House.class);
Path<Garage> garage = houseRoot.get(House_.garage);
Path<Long> garageId = garage.get(Garage_.id);
query.where(cb.or(cb.equal(garageId , 0), cb.isNull(garage)));
TypedQuery<House> typedQuery = entityManager.createQuery(query);
List<House> houses = typedQuery.getResultList();
This results in the following SQL:
SELECT H_ID, NAME, G_ID FROM HOUSE WHERE ((G_ID = 0) OR (G_ID IS NULL));
I stumbled over a very strange behaviour with JPA Eclipselink and PostgreSQL.
create table test(id bigint, name varchar(255))
insert into test values(1, "hello")
insert into test values(2, null)
Java EE Entity Bean:
#Entity
#Table(name = "test")
public class Test implements Serializable {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
Stateless Bean with simple JPA Query:
TypedQuery<Test> q = em.createQuery("select t from Test t where t.name = :name", Test.class);
q.setParameter("name", null);
List<Test> list = q.getResultList();
for (Test t : list) {
System.out.println(t.getId() + " " + t.getName());
}
Result:
Aspected: Row with id=2 with null in name field
Current behaviour: No rows returned
When I use "is null" instead of example above in JPA Query. Then I do get something back.
What does JPA do?
select * from test where t.name = null
should this not be the same as
select * from test where t.name is null
JPQL also has the 'is null' comparator that you are required to use to compare to null values. While some JPA providers can interpret and determine that the value passed into an equality is null, and therefore use "IS NULL" in the generated SQL, this is generally not viable when using parameters when the value can change as it means the statement underneath the query must change as well.
I use JPA 2.0 criteria builder. I need get data from one table and sort them by column from other. This tables have relations OneToMany:
class Club{
#OneToMany(mappedBy = "club")
private List<Address> addresses;
...
}
class Address{
#JoinColumn(name = "club_id", referencedColumnName = "id")
#ManyToOne(fetch = FetchType.LAZY)
private Club club;
#Column(name = "type")
private Long type;
#Column(name = "full_address")
private String full_address;
...
}
May be several address of some type but I need only one row of this specific address.
I write native queries with subquery, but it's has problem because subquery doesn't use in order clause and in select clause in JPA 2.0.
select c.full_name from club c
ORDER BY (select a.full_address from address a WHERE c.id= a.club_id and a.type=1 LIMIT 1)
select c.full_name, (select a.full_address from address a WHERE a.type=1 AND c.id=a.club_id LIMIT 1) as full_address FROM club c
ORDER BY fullAddress;
How I can replace native order by clause on JPA equivalent?
Thanks!
This native query also resolve problem and it can replace by JPA query
select c.full_name, min(a.full_address) FROM club c LEFT JOIN address a on c.id = a.club_id
where a.id is null or a.type=1 or not exists(SELECT 1 from address aSub WHERE aSub .club_id=c.id AND aSub.type=1)
GROUP BY c.id, c.full_name ORDER BY min(a.full_address);
JPA equivalent
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<ClubItem> query = builder.createQuery(ClubItem.class);
Root<Club> root = query.from(Club.class);
Join<Club, Address> addressJoin = root.join(Club_.address, JoinType.LEFT);
query.select(builder.construct(ClubItem.class, root.get(Club_.id), root.get(Club_.fullName), builder.function("min", String.class, addressJoin.get(Address_.fullAddress))));
Subquery<Address> subquery = query.subquery(Address.class);
Root<Address> addressRoot = subquery.from(Address.class);
subquery.select(addressRoot);
subquery.where(
builder.and(
builder.equal(addressRoot.get(Address_.type), 1),
builder.equal(addressRoot.get(Address_.clubId), root.get(Club_.id))));
query.where(builder.or(builder.isNull(addressJoin), builder.equal(addressJoin.get(Address_.type), builder.literal(new Long(1))),
builder.not(builder.exists(subquery))));
query.groupBy(root.get(Club_.id), root.get(Club_.fullName))
Order order = builder.asc(builder.function("min", String.class, addressJoin.get(Address_.fullAddress)));
query.orderBy(order);
TypedQuery<ClubItem> contentQuery = em.createQuery(query);
It's not terribly elegant, but it gets the job done...
Make your "Club" class implement Comparable. Put the order-by logic into the Comparable. Then use Collections.sort(unsortedList) to get the list into sorted form. There's also a Collections.sort(unsortedList, Comparable) method which could be useful, especially if you are doing a bunch of similar methods that just vary on order-by.
I have two Entities related by a ManyToMany and I want to select them via a named Query. This works in my test (with a H2 DB set up) and throws exceptions at runtime (with postgresql set up). Other than the H2 and PG I am hard pressed to find differences between test and production.
The Entities and the Query look like so (abbreviated):
#Entity(name = "Enrichment")
#Table(name = "mh_Enrichment")
NamedQueries({
#NamedQuery(name = "findByLink",
query = "SELECT e FROM Enrichment e INNER JOIN e.links l WHERE l.link in (:links)") })
public class EnrichmentImpl {
#Id
#Column(name = "enrichmentId")
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToMany
#JoinTable(name = "mh_EnrichmentLinks", joinColumns = { #JoinColumn(name = "EnrichmentId",
referencedColumnName = "enrichmentId") }, inverseJoinColumns = { #JoinColumn(name = "Link",
referencedColumnName = "link") })
private List<Link> links;
}
#Entity(name = "Link")
#Table(name = "mh_enrichment_link")
public class LinksImpl {
#Id
#Column(name = "link", length = 1024)
private String link;
}
Upon running the query with a String value in production I get:
Internal Exception: org.postgresql.util.PSQLException: ERROR: operator does not exist: character varying = bigint
Hinweis: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Position: 215
Error Code: 0
Call: SELECT t1.enrichmentId FROM mh_enrichment_link t0, mh_EnrichmentLinks t2, mh_Enrichment t1 WHERE ((t0.link IN (?)) AND ((t2.EnrichmentId = t1.enrichmentId) AND (t0.link = t2.Link)))
Any ideas what's wrong? It is the query, isn't it?
The query is supposed to retrieve a list of Enrichments that are related to the given link.
Update #1
As requested: the tables in the DB look as follows:
For entity Link
CREATE TABLE mh_enrichment_link
(
link character varying(1024) NOT NULL,
CONSTRAINT mh_enrichment_link_pkey PRIMARY KEY (link)
)
For entity Enrichment
CREATE TABLE mh_enrichment
(
enrichmentid bigint NOT NULL,
CONSTRAINT mh_enrichment_pkey PRIMARY KEY (enrichmentid)
)
For the relation (See answer, this was where it went wrong)
CREATE TABLE mh_enrichmentlinks
(
link character varying(1024) NOT NULL,
CONSTRAINT mh_enrichment_link_pkey PRIMARY KEY (link)
)
The issue was fixed by dropping all related tables and having JPA regenerate them. Table definitions didn't match Entity definitions.
Thats also the quite obviously the reason why the test worked and the production didn't. In testing the tables are generated on runtime, in production they existed already (with an outdated definition).
Side note: The query is correct and does what it should.