Spring Data JPA self join query giving unexpected result - spring-data-jpa

I have a mysql database table 'Faculties' like below,
My entity class (FacultyEntity) is as below
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Faculty_Id")
private Integer facultyId;
#Column(name = "Faculty_Name", length = 100, nullable = false, unique = true)
private String facultyName;
#ManyToOne(cascade = { CascadeType.REFRESH })
#JoinColumn(name = "HOD_ID", nullable = true)
private FacultyEntity hodId;
I want to display the result in my application sorted by HOD's and and then faculties. This is what I've tried
#Query("from FacultyEntity f ORDER BY f.hodId.facultyName, f.facultyName")
But I'm getting total 4 rows sorted like this. I think it is because of the null value in the HOD_ID column, But I'm not sure. I'm newbie here.
The expected result will be
Please help me to fix this issue.

When you do f.hodId in the query, it implicitly translates to inner join, which eliminates the two rows with null in HOD_Id column.
Try this instead
#Query("from FacultyEntity f left join f.hodId hod ORDER BY hod.facultyName, f.facultyName")

Related

EclipseLink ManyToOne - CriteriaBuilder Generated Query is Wrong

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));

Syntax error in SQL statement org.h2.jdbc.JdbcSQLException:

Hi this is my repository
#Repository
interface GuestRepository : JpaRepository<Guest, Long> {
#Query("from Guests g where g.date>=:fromDate and g.date<=:toDate ",nativeQuery = true)
fun findInPeriod(
#Param("fromDate") fromDate: Date
, #Param("toDate") toDate: Date
, pageRequest: Pageable
): Page<Guest>
}
and this is my guest object
#Entity
#Table(name = "Guests")
data class Guest(
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "guest_seq")
#SequenceGenerator(name = "guest_seq", sequenceName = "guest_seq", allocationSize = 1)
val id: Long=0
,
#Column(length = 50, unique = true)
#NotNull
#Size(min = 4, max = 50)
val userName: String="username"
, val firstName: String?=null
, val lastName: String?=null
,
#Temporal(TemporalType.TIMESTAMP)
#NotNull
val date: Date=Date()
)
I use spring and h2 but when I run my application it show me this error message
org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "FROM GUESTS G WHERE[*] G.DATE>=? AND G.DATE<=? LIMIT ? "; expected "(, USE, RIGHT, LEFT, FULL, INNER, JOIN, CROSS, NATURAL, ,, SELECT"; SQL statement:
With nativeQuery = true you tell Spring Data JDBC to treat your query as a native, i.e a SQL, query.
But the query you provide is not legal SQL.
It looks very much like HQL which is a Hibernate specific dialect of JPQL.
To fix the problem remove the nativeQuery = true part or use SQL

query for nextval sequence generation taking long time

I have a process in application where we insert 1000+ records in application. There is a attribute in the table as mentioned below.
#Index(name = "_IDX_UNIQUE_UUID", columnNames = {"UUID"})
#Column(name = "UUID", nullable = false)
private Long uuid;
During populating the entity class the value is set as following
entity.setUuid(service.getNextUuid());
which calls this method in service class:
public Long getNextUuid() {
Query query = entityManager.createNativeQuery("select nextval('seq_xxxx_uuid')");
//This takes lot of time to execute
Obj result = query.getSingleResult();
return ((BigInteger) result).longValue();
}
To get next value from sequence, it takes around 200-700 ms. This accumulates to several minutes if I try to insert 1000+ records. Moreover, the query is quite simple and executes in less than 1ms if I execute it in a database client.
I am using postgresql 9.4-1206-jdbc4, hibernate 4.2.0.Final and Spring 4.2.5.
It's slow, because you're going through all the layers of security and consistency checks of a JPA full query, plus you're creating a new NativeQuery object every time and not doing it though a #NamedNativeQuery, thus taking even more time to evaluate.
The solution is to annotate the column with:
#Id
#Index(name = "_IDX_UNIQUE_UUID", columnNames = {"UUID"})
#SequenceGenerator(name = "seq_xxxx_uuid_gen", sequenceName = "seq_xxxx_uuid", allocationSize = 1)
#GeneratedValue(generator = "seq_xxxx_uuid_gen")
#Column(name = "UUID", nullable = false)
private Long id;
You do want that allocationSize to be the same value as the increment_by value of your sequence, or you'll have other problems after the first persist.
If you need to use that Id for multiple objects (say it's part of a composite key, but only part of it is serial-generated), just grab it from the first one after persisting it.
Edit:
Alternatively you can forego the SequenceGenerator and use this:
#GeneratedValue(strategy = GenerationType.IDENTITY)
It may be faster, if the generated query is properly optimized and uses java.sql.Statement.getGeneratedKeys() upon insert.

How replace native order by clause on JPA equivalent?

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.

JPQL Query working in testing, not in production

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.