OneToMany + JoinTable + OrderColumn: Order is written correctly but not read - eclipselink

I have an entity that can consist of itself:
class Group {
// ...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(
name = "group_group",
joinColumns = #JoinColumn(name = "id"),
inverseJoinColumns = #JoinColumn(name = "parentgroup_id")
)
#OrderColumn
private List<Group> groups = new ArrayList<Group>();
#ManyToOne
private Group parentGroup;
public List<Group> getGroups() {
return groups;
}
public Group getGroup() {
return parentGroup;
}
}
I can create a group and add two child groups:
Group parent = new Group();
Group child1 = new Group();
Group child2 = new Group();
parent.getGroups().add(child1);
parent.getGroups().add(child2);
parent = groupRepository.save(parent);
// parent.getGroups().get(0).getId() == child1.getId()
// parent.getGroups().get(1).getId() == child2.getId()
But this seems to be a coincidence. I am able to update the order (e.g. using Collections.sort) and the join table rows are updated correctly.
It does not matter how I load the parent group, the child groups are always in the order of creation. The executed SQL query is:
SELECT t1.ID, t1.PARENTGROUP_ID FROM GROUP t0, GROUP t1 WHERE ((t1.PARENTGROUP_ID = ?) AND (t0.ID = t1.PARENTGROUP_ID))
There is no ORDER BY which seems wrong. How can I persuade EclipseLink to add it?

The problem was that I tried to load the child group entities using a CrudRepository. The repository ignores the #OrderColumn annotation. I fixed it by fetching the parent group and using getGroups().

Try using the #OrderBy which does not rely on the database. You will need to provide a field from Group to order by (note field not column).
#OrderBy("fieldNameGoesHere DESC")
private List<Group> groups = new ArrayList<Group>();
OrderColumn will maintain the order of the list within the database, which carries additional performance and maintenance overhead. OrderBy puts the onus on the persistence provider to maintain the order of the entities, however the order will not persist across persistence contexts.
For more information, checkout this blog post I created regarding #OrderBy.

Related

Subquery - JPA - Include Subquery as part of the main selection

I have an issue to add the result of a subquery as part of the selection.
This is how my data model looks like:
#Entity
#Table(name = "flow")
public class Flow {
#Id
Long id;
#OneToMany(mappedBy="flow", fetch = FetchType.LAZY)
Set<Subscription> subscribers;
... other internal fields
//extra fields that come from other tables
#Transient //as it is not part of the model sometimes must be null
Long numberOfActiveSubscribers;
}
#Entity
#Table(name = "subscriptions")
public class Subscription {
#Id
Long id;
#ManyToOne
#JoinColumn(name = "idflow")
Flow flow;
#OneToMany(name = "user")
User user;
#Column(name = "active")
boolean active;
}
I need to implement a query using jpa specifications that will include in its definition the number of user that have active subscriptions to the flow. So that I will be able to use pagination over that, including sort by this field that is not part of the original flow table. The SQL query I came out with look like this:
SELECT f.*,
(SELECT count(sf.id)
FROM subscription sf
WHERE sf.active = true
AND sf.idf = f.id) as numberofactivesubscribers,
FROM flow f;
I would like to us it in a findAll method like so:
this.repository.findAll(new Specification<Flow>() {
#Override
public Predicate toPredicate(Root<Flow> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
Subquery<Long> sq = query.subquery(Long.class);
Root<Subscription> subsRoot = sq.from(Subscription.class);
sq.select(cb.count(subsRoot.get("id")))
.where(cb.equal(subsRoot.get("flow"), flowRoot),
cb.equal(subsRoot.get("active"), true));
//I DONT KNOW HOW TO INCLUDE THIS AS A PART OF THE MAIN SELECT OF THIS QUERY
}
}, pagination);
But as you can see I don't know how to include the subquery as part of the select method as I did in my SQL query.

How to search two tables sharing a foreign key (I think I'm asking this right....)?

Dog entity
#Entity(tableName = "dog_table")
public class DogEntity {
private int mId;
private String mName, mBreed;
etc..
}
Toy entity
#Entity(tableName = "toy_table")
public class ToyEntity {
private int mId;
private String mName, mBrand;
etc..
}
DogAndToy join table entity
#Entity(tableName = "dog_and_toy_join_table",
primaryKeys = {"mDogID", "mToyId"},
foreignKeys = {
#ForeignKey(
entity = DogEntity.class,
parentColumns = "mId",
childColumns = "mDogID",
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
#ForeignKey(
entity = ToyEntity.class,
parentColumns = "mId",
childColumns = "mToyId",
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
},
indices = {#Index("mDogID"), #Index("mToyId")}
)
public class DogAndToyJoinEntity{
private final int mDogID, mToyId;
public DogAndToyJoinEntity(int mDogID, int mToyId) {
this.mDogID = mDogID;
this.mToyId = mToyId;
}
etc..
}
DogAndToy data class
public class DogAndToy {
#Embedded
public Dog mDog;
#Relation(
parentColumn = "mId",
entityColumn = "mId",
entity = ToyEntity.class,
associateBy =
#Junction(
value = DogAndToyJoinEntity.class,
parentColumn = "mDogId",
entityColumn = "mToyId"
)
)
public List<ToyEntity> toyList;
}
notes: All dogs can have multiple toys, and toys can be associated with multiple dogs. Dog & Toy entities don't share any fields (eg - Dog doesn't have toyId, etc)
I've been trying for a few days to wrap my head around
how to query/get all dogs associated with one Toy (by name)
I use the DogAndToy data class for display purposes in my RecyclerView.
JOIN and INNER JOIN queries are baffling to me and I've been trying multiple variations but keep ending up with zero search results. Here's my most recent try:
#Transaction
#Query("SELECT dog_table.* FROM dog_table" +
"INNER JOIN dog_and_toy_join_table ON dog_table.mId = dog_and_toy_join_table.mDogId" +
"INNER JOIN toy_table ON toy_table.mId = dog_and_toy_join_table.mToyId " +
"WHERE toy_table.mName LIKE :query")
LiveData<List<DogAndToy>> findDogsByToyName(String query);
Can anyone suggest a step-by-step description of these queries in Android Room? Any of the JOIN articles/examples I find here or anywhere on the internets don't have a "join" (foreign key) reference...
Am I even trying this in the right manner?
update: to clarify, I have FTS tables and my "basic" searches work fine (eg - search by name, etc)
Replace :toyName with the variable
SELECT d.mName FROM dog_table AS d
WHERE d.mId IN (
SELECT j.mDogID FROM dog_and_toy_join_table AS j
WHERE j.mToyId = (
SELECT t.mId FROM toy_table AS t
WHERE t.mName = :toyName));
EDIT
TBH, no idea why it only selects one row. Maybe someone else can answer it.
It the mean time take this:
select d.mName
from dog_table d
INNER join dog_and_toy_join_table dt
on d.mid = dt.mDogID
INNER JOIN toy_table t
ON dt.mToyId = t.mId
WHERE t.mName = 'toy1'

How to map ONE-TO-MANY native query result into a POJO class using #SqlResultSetMapping

Im working in a backend API using Java and MySql, and I'm trying to use #SqlResultSetMapping in JPA 2.1 for mapping a ONE-TO-MANY native query result into a POJO class, this is the native query:
#NamedNativeQuery(name = "User.getAll”, query = "SELECT DISTINCT t1.ID, t1.RELIGION_ID t1.gender,t1.NAME,t1.CITY_ID , t2.question_id, t2.answer_id FROM user_table t1 inner join user_answer_table t2 on t1.ID = t2.User_ID“,resultSetMapping="userMapping")
And, here is my result SQL mapping:
#SqlResultSetMapping(
name = "userMapping",
classes = {
#ConstructorResult(
targetClass = MiniUser.class,
columns = {
#ColumnResult(name = "id"),
#ColumnResult(name = "religion_id"),
#ColumnResult(name = "gender"),
#ColumnResult(name = "answers"),
#ColumnResult(name = "name"),
#ColumnResult(name = "city_id")
}
),
#ConstructorResult(
targetClass = MiniUserAnswer.class,
columns = {
#ColumnResult(name = "question_id"),
#ColumnResult(name = "answer_id")
}
)
})
And, here is the implementation of the POJO classes: (I just removed the constructor and the getters/setter)
MiniUser class
public class MiniUser {
String id;
String religionId;
Gender gender;
List<MiniUserAnswer> answers;
String name;
String city_id;
}
and the MiniUserAnswer class
public class MiniUserAnswer {
String questionId;
String answerId;
}
My goal is to execute this Query and return a list of MiniUser, and in each MiniUser: a list of his “answers", which is a list of MiniUserAnswer.
after running this code, I got this error:
The column result [answers] was not found in the results of the query.
I know why, it's because there is no “answers" field in the query select statement.
So, how can I accomplish something like this, considering the performance? This answers list may reach 100.
I really appreciate your help, Thanks in advance!
The query "SELECT DISTINCT t1.ID, t1.RELIGION_ID t1.gender, t1.NAME, t1.CITY_ID, t2.question_id, t2.answer_id" does not return a parameter called answers.
To obtain the result you are looking for I would use:
Option 1 (Criteria Builder)
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<UserTableEntity> cq = cb.createQuery(UserTableEntity.class);
Root<UserTableEntity> rootUserTable = cq.from(UserTableEntity.class);
Join<UserTableEntity,UserAnswerTableEntity> joinAnswerTable = rootUserTable.join(rootUserTable_.id) // if the relationship is defined as lazy, use "fetch" instead of "join"
//cq.where() NO WHERE CLAUSE
cq.select(rootUserTable)
entityManager.createQuery(cq).getResultList();
Option 2 (Named query, not native)
#NamedQuery(name = "User.getAll”, query = "SELECT t1 FROM UserTableEntityt1 join fetch t1.answers)
Option 3 (Entity subgraph, new in JPA 2.1)
In User Entity class:
#NamedEntityGraphs({
#NamedEntityGraph(name = "graph.User.Answers", attributeNodes = #NamedAttributeNode("answers"))
})
In DAO set hints in the entity manager:
EntityGraph graph = this.em.getEntityGraph("graph.User.Answers");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);

deleting entries with JPA and subqueries

I just wrote an sql query :
DELETE FROM basisgegevens.gm_persoonburgstaat pbs
WHERE (pbs.ingangsdatum, pbs.id_persoon) in (
SELECT pbs2.ingangsdatum, pbs2.id_persoon
FROM basisgegevens.gm_persoonburgstaat pbs2
WHERE pbs2.ingangsdatum = pbs.ingangsdatum
AND pbs2.id_persoon = :persoonID
AND pbs2.id_persoonburgerlijkestaat > pbs.id_persoonburgerlijkestaat);
I need to rewrite it to JPQL, but am getting stuck with the subquery refrencing the outer query.
public class PersoonBurgerlijkeStaatEntity {
#Column(name = "id_persoonburgerlijkestaat"
private Long identifier;
private Date ingangsdatum;
#ManyToOne
#JoinColumn(name = "id_persoon", referencedColumnName = "id_persoon", nullable = false)
private PersoonEntity persoon;
}
The persoon entity has an identifier
Can someone help me rewrite this?
Thanks
Not sure about this but give a try.
DELETE FROM persoonburgstaat person where (person.ingangsdatum, person identifier) in
(select p.ingangsdatum, p.identifier from persoonburgstaat p
left join p.persoon per where per.id_persoon = :persoonID
AND per.id_persoonburgerlijkestaa > p.identifier)
the left join will make the outer query
But to be more sure post PersoonEntity entity as I think " id_persoonburgerlijkestaa " is the name of the column not the property and query will fail based on that.

How to eager fetch join table collection in Hibernate Native SQL Query?

I have following three database tables
Customer
Product
CustomerProductRelation
Corresponding to these tables, I have two Hibernate POJO's
Product
Customer
One of the member variable is a joinTable:
#JoinTable(name = "CustomerProductRelation", joinColumns = { #JoinColumn(name = "CUSTOMER_ID") }, inverseJoinColumns = { #JoinColumn(name = "PRODUCT_ID") })
private List<Product> products;
Due to some reason, I need to use a native SQL query on Customer table, in that case how do I eager fetch products in my customer list?
I am doing something similar to this:
String queryString = "select c.*,cpr.product_id from Customer c, CustomerProductRelation cpr where c.customer_id = cpr.customer_id";
List list = getSession().createSQLQuery(queryString)
.addEntity("c", Customer.class)
.addJoin("p", "c.products").list();
This does not seem to work. The exception is as follows:
java.lang.NullPointerException at org.hibernate.loader.DefaultEntityAliases.<init>(DefaultEntityAliases.java:37) at org.hibernate.loader.ColumnEntityAliases.<init>(ColumnEntityAliases.java:16) at org.hibernate.loader.custom.sql.SQLQueryReturnProcessor.generateCustomReturns(SQ‌​LQueryReturnProcessor.java:264)
Please let me know if anyone knows the solution to this.
Is this what you are seeing? (HHH-2225)