I'm trying to implement a query like this in JPA:
SELECT
ta.field_aggr_1,
ta.field_aggr_2,
MIN(tb.date_inv) AS min_date_inv,
MAX(tb.date_inv) AS max_date_inv
FROM table_a ta
INNER JOIN table_b tb ON ta.idB = tb.id
GROUP BY
ta.field_aggr_1,
ta.field_aggr_2
The key point is the MIN and MAX functions that apply in one column of a joined table.
I've created the entities:
#Entity
#Table(name="table_a")
public class EntityA extends Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="field_aggr_1")
private String field_aggr_1;
#Column(name="field_aggr_2")
private String field_aggr_2;
#ManyToOne
#JoinColumn(name="idB")
private EntityB entityB;
// Getters & Setters & HashCode & equals & toString
}
#Entity
#Table(name="table_b")
public class EntityB extends Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="date_inv")
private String date_inv;
// Getters & Setters & HashCode & equals & toString
}
And in the service I want to create query:
EntityManager em = ...;
Root<EntityA> root = criteriaQuery.from(EntityA.class);
EntityType<EntityA> type = this.em.getMetamodel().entity(EntityA.class);
Join<EntityA, EntityB> join = root.join(type.getDeclaredSingularAttribute("entityB", EntityB.class));
List<Selection<?>> fields = new ArrayList<Selection<?>>();
// grouping fields
fields.add(root.<EntityA>get("field_aggr_1"));
fields.add(root.<EntityA>get("field_aggr_2"));
I've managed to include fields from the joined table,
fields.add(join.<EntityB>get("date_inv"));
BUT I haven't succeed in implement the min aggregation.
Thanks in advance for your answers!
I have managed to solve the question. First, I needed to have one more "root" and one more "entityType" for the joined entity:
Root<EntityB> rootB = criteriaQuery.from(EntityB.class);
EntityType<EntityB> typeB = this.em.getMetamodel().entity(EntityB.class);
With these, now I can do what I needed:
fields.add(builder.least(rootB.get(typeB.getDeclaredSingularAttribute("date_inv", String.class))));
fields.add(builder.greatest(rootB.get(typeB.getDeclaredSingularAttribute("date_inv", String.class))));
Hope that it helps someone!
Related
Given this very simple DTO:
#Entity
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
#OneToOne
private Employee boss;
}
I'd like to make a query that gathers all employee names and their boss' id, put in a nice clean POJO:
public class EmployeeInfo {
private String name;
private Long bossId;
public EmployeeInfo(String name, Long bossId) {
this.name = name;
this.bossId = bossId;
}
}
This query should be of use:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<EmployeeInfo> query = cb.createQuery(EmployeeInfo.class);
Root<Employee> root = query.from(Employee.class);
query.select(
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
root.get("boss").get("id").as(Long.class)));
result = em.createQuery(query).getResultList();
When a bossId is present in the employee column this works just fine. But when no boss id is set the record will be completly ignored. So how do i treat this non existing boss relation as null or 0 for the construct/multiselect?
In pure SQL it is easy:
SELECT name, COALESCE(boss_id, 0) FROM EMPLOYEE;
But for the love of god i cannot make the criteria api do this.
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
cb.coalesce(root.get("boss").get("id").as(Long.class), 0L)));
The problem is that root.get("boss") generate query with cross join like this from Employee employee, Employee boss where employee.boss.id=boss.id. So records where employee.boss.id is null are ignored.
To solve the problem you should use root.join("boss", JoinType.LEFT) instead of root.get("boss")
I have faced with issue trying to select for update row using Spring data with Hibernate as JPA implementation and Postgresql.
Suppose we have entities:A,B,C.
public class A{
#Id
private Long id;
#OneToMany(fetch = FetchType.EAGER)
private Set<B> bSet;
#OneToMany(fetch = FetchType.EAGER)
private Set<C> cSet;
}
Suppose we want to select A with all related B and C entities for update i.e. with locking row related to A table.
#Query(SELECT a FROM A a
LEFT JOIN FETCH a.bSet
LEFT JOIN FETCH a.cSet
WHERE a.id=?)
#Lock(LockModeType.PESSIMISTIC_WRITE)
public A selectAndLockA(Long Aid);
The query will look like
SELECT a.column1, ... from tableA a LEFT JOIN tableB b ... FOR UPDATE of a,c
FOR UPDATE of a,c
The query will try to lock two tables what leads to exception like :
org.postgresql.util.PSQLException: ERROR: FOR UPDATE cannot be applied to the nullable side of an outer join
What I try to archive is locking only first table "FOR UPDATE OF a"
Is it possible to configure somehow or tell Hibernate to lock only first table.
This is not supported by PostreSQL. If you do an outer SELECT nothing can prevent somebody from inserting a row into the LEFT JOINED table thereby modifiying the result set you are looking at (e.g. the columns would not be NULL anymore on a repeated read).
For a detailed explanantion see here
It's been a long time since question was created, but I have a similar problem and hope my answer will help somebody.
Suppose that we have this JPA entities:
#Entity
#Table(name = "card_transactions")
public class CardTransactionsEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "card_trans_seq")
#SequenceGenerator(name = "card_trans_seq", sequenceName = "card_trans_seq")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "ofd_id", referencedColumnName = "ofd_id"),
#JoinColumn(name = "receipt_id", referencedColumnName = "receipt_id")})
private ReceiptsEntity receipt;
#Column
#Enumerated(EnumType.STRING)
private CardTransactionStatus requestStatus;
...
}
#Entity
#Table(name = "receipts")
public class ReceiptsEntity {
#EmbeddedId
private OfdReceiptId id;
...
}
#Embeddable
public class OfdReceiptId implements Serializable {
#Column(name = "ofd_id")
#Enumerated(EnumType.STRING)
private OfdId ofdId;
#Column(name = "receipt_id")
private String receiptId;
...
}
And we want select CardTransactionsEntity with fetched ReceiptsEntity for pessimistic update only CardTransactionsEntity. This can be done using Hibernate and Spring Data JPA repository as
public interface CardTransactionRepository extends JpaRepository<CardTransactionsEntity, Long> {
#Query("select ct from CardTransactionsEntity ct left join fetch ct.receipt r where ct.requestStatus = :requestStatus")
#Lock(value = LockModeType.PESSIMISTIC_WRITE)
#QueryHints(value = {
#QueryHint(name = "javax.persistence.lock.timeout", value = "-2"), // LockOptions.SKIP_LOCKED
#QueryHint(name = "org.hibernate.lockMode.r", value = "NONE") // "r" is alias for ct.receipt and will excluded from PESSIMISTIC_WRITE
})
List<CardTransactionsEntity> loadCardTransactions(#Param("requestStatus") CardTransactionStatus requestStatus, Pageable pageable);
}
This repository method will execute query like
SELECT ct.*, r.* from card_transactions ct LEFT OUTER JOIN receipts r ON ct.ofd_id = r.ofd_id and ct.receipt_id = r.receipt_id WHERE ct.request_status=? LIMIT ? FOR UPDATE OF ct SKIP LOCKED
You can bypass this error with joining the tables with FetchType.LAZY. This fetch type is the default one and it is not required to specify for #OneToMany joins.
public class A{
#Id
private Long id;
#OneToMany
private Set<B> bSet;
#OneToMany
private Set<C> cSet;
}
How to invoke order by on a joined entity? I am trying to achieve the following with:
select * from person p inner join telephone t on p.id=t.person_id join sim s on s.id=t.sim_id order by s.name DESC
#Entity
public class Person implements Serializable{
#Id
private Long id;
#OneToMany(orphanRemoval = true, mappedBy = "person", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
private List<Telephone> telephonesNumber;
#Entity
public class Telephone implements Serializable {
#Id
private String number;
#Id
#ManyToOne()
#JoinColumn(name = "person_id")
private Person person;
#Id
#ManyToOne(cascade = {})
#JoinColumn(name = "sim_id")
private Sim sim;
#Entity
public class Sim implements Serializable {
#Id
private Long id;
#Column(unique = true)
private String name;
I use specification interface, in this example sorting is on the field person.id and it works
public class PersonSpecification implements Specification<Person> {
#Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
// there is many different conditions for example
// if(someCondition!=null) {
// predicates.add(builder.like(root.get("someProperty"), someValue));
// }
query.groupBy(root.get("id"));
//there I want to order by Sim.name i dont know how
query.orderBy(builder.asc(root.get("phone")));//this works
return builder.and((predicates.toArray(new Predicate[predicates.size()])));
}
I want to order by Sim.name but i dont know how.
In JPA specification you can use:
query.orderBy(builder.asc(root.join("telephonesNumber").get("sim").get("name")));
to sort by sim name.
For more details:
https://en.wikibooks.org/wiki/Java_Persistence/Querying#Joining.2C_querying_on_a_OneToMany_relationship
If you using JPA Query:
#Query("select s from Person p
join p.telephonesNumber t
join t.sim s order
by t.sim.id desc")
It will produce this:
select * from person p
inner join telephone t on p.id=t.person_id
inner join sim s on t.sim_id=s.id
order by t.sim_id desc
For more details:
https://github.com/abhilekhsingh041992/spring-boot-samples/blob/master/jpa/src/main/java/example/springboot/jpa/repository/PersonRepository.java
another way for that would be using Query method:
List<Telephone> findAllByOrderBySimIdAsc();
Look at this findAllByOrderBySimIdAsc
With the code before, you can get all rows from Telephone ordered by Sim Id.
I'm using QueryHints in Spring Data JPA to use EclipseLink Batch Fetch with a type of IN. Ultimately, I need to use this around 30 fields but it doesn't seem to work right for 2 fields. Field A has a ManyToOne relationship and Field B has a ManyToMany. Based on the results of the initial query, I would expect the batch hint to generate an IN clause with 2 ids for Field A and 12 for Field B. This works fine when the hint is turned on for one field at a time. When it is enabled for both fields, the hint only applies to whichever field is the last hint in the list of QueryHints. I've tried EAGER and LAZY fetch on the fields as a shot in the dark, but it had not impact.
Is there a limitation with mixing batch fetch hints based on the relationship type? Is there something different going on? The EclipseLink documentation isn't very detailed for this feature.
EDIT: It seems it doesn't matter what fields I enable it only, it only works for one at at time. Here is sample code for two entities. The BaseEntity defines the PK id generation.
#Entity
#Table(name = "MainEntity")
public class MainEntity extends BaseEntity implements Cloneable {
...
#ManyToMany(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
#JoinTable(
name="EntityBMapping",
joinColumns={#JoinColumn(name="mainId", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="bId", referencedColumnName="id")})
#JsonIgnore
private Set<EntityB> bSet = new HashSet<>();
#ManyToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#JoinTable(
name="EntityAMapping",
joinColumns={#JoinColumn(name="mainId", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="aId", referencedColumnName="id")})
#JsonIgnore
#OrderColumn(name="order_index", columnDefinition="SMALLINT")
private List<EntityA> aList = new ArrayList<>();
...
}
#Entity
#Cache(type=CacheType.FULL)
#Table(name = "EntityA")
public class EntityA extends BaseEntity {
#Column(name = "name", columnDefinition = "VARCHAR(100)")
private String name;
#ManyToMany(mappedBy = "entityASet", fetch=FetchType.LAZY)
#JsonIgnore
private Set<MainEntity> mainEntityList = new HashSet<>();
}
#Entity
#Cache(type=CacheType.FULL)
#Table(name = "EntityB")
public class EntityB extends BaseEntity {
#Column(name = "name", columnDefinition = "VARCHAR(100)")
private String name;
#ManyToMany(mappedBy = "entityBSet", cascade=CascadeType.ALL)
#JsonIgnore
private Set<MainEntity> mainEntityList = new HashSet<>();
}
The repository query:
#QueryHints(value = {
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH_TYPE, value = "IN"),
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH_SIZE, value = "250"),
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH, value = "o.aList")},
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH, value = "o.bSet")},
forCounting = false)
List<MainEntity> findAll(Specification spec);
Generated queries:
SELECT id, STATUS, user_id FROM MainEntity WHERE ((STATUS = ?) OR ((STATUS = ?) AND (user_id = ?)))--bind => [ONESTAT, TWOSTAT, myuser]
..
SELECT t1.id, t1.name, t0.order_index FROM EntityAMapping t0, EntityA t1 WHERE ((t0.mainId = ?) AND (t1.id = t0.aId))--bind => [125e17d2-9327-4c6b-a65d-9d0bd8c040ac]
...
SELECT t1.id, t1.name, t0.mainId FROM EntityBMapping t0, EntityB t1 WHERE ((t1.id = t0.bId) AND (t0.mainId IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)))--bind => [125e17d2-9327-4c6b-a65d-9d0bd8c040ac, 1c07a3a9-7028-48ba-abe8-2296d58ebd57, 235bb4f2-d724-4237-b73b-725db2b9ca9f, 264f64b3-c355-4476-8530-11d2037b1f3c, 2d9a7044-73b3-491d-b5f1-d5b95cbb1fab, 31621c93-2b0b-4162-9e42-32705b7ba712, 39b33b19-c333-4523-a5a7-4ba0108fe9de, 40ba7706-4023-4b7e-9bd5-1641c5ed6498, 52eed760-9eaf-4f6a-a36f-076b3eae9297, 71797f0c-5528-4588-a82c-5e1d4d9c2a66, 89eda2ef-80ff-4f54-9e6a-cf69211dfa61, 930ba300-52fa-481c-a0ae-bd491e7dc631, 96dfadf9-2490-4584-b0d4-26757262266d, ae079d02-b0b5-4b85-8e6f-d3ff663afd6e, b2974160-33e8-4faf-ad06-902a8a0beb04, b86742d8-0368-4dde-8d17-231368796504, caeb79ce-2819-4295-948b-210514376f60, cafe838f-0993-4441-8b99-e012bbd4c5ee, da378482-27f9-40b7-990b-89778adc4a7e, e4d7d6b9-2b8f-40ab-95c1-33c6c98ec2ee, e557acf4-df01-4e66-9d5e-84742c99870d, ef55a83c-2f4c-47b9-99bb-6fa2f5c19a76, ef55a83c-2f4c-47b9-99bb-6fa2f5c19a77]
...
SELECT t1.id, t1.name, t0.order_index FROM EntityAMapping t0, EntityA t1 WHERE ((t0.mainId = ?) AND (t1.id = t0.aId))--bind => [1c07a3a9-7028-48ba-abe8-2296d58ebd57]
As Chris mentioned, Named Queries are the best work around for this issue. The other option is to use a custom repository and call setHint on the EntityManager yourself for each hint specified (plenty of examples out there for creating custom repos in Spring Data JPA). You could attempt to override findOne(...) and protected <S extends T> TypedQuery<S> getQuery(Specification<S> spec, Class<S> domainClass, Sort sort) on SimpleJpaRepository to try and create a generic way to properly set the hints but you'll likely want to check that you don't duplicate hint setting on getQuery(...) as you'll still want to call super() for that and then apply your additional hints before returning the query. I'm not sure what the behavior would be if you applied a duplicate hint. Save yourself the trouble and use Named Queries is my advice.
I have implemented Joined, Multiple Table Inheritance.
There is a 'parent' table pois and two sub tables: xPois and yPois and in turn I have an abstract PoiDao class as well as a XPoiDao and a YPoiDao class extending PoiDao.
A poi may have multiple reservations but a reservation belongs to exactly one poi.
Named queries defined in the child table DAOs work well for attributes defined in the respective (direct) table hierarchy. The parent table has a foreign key relationship to another table named reservations (table reservations holds the foreign key of table pois). The problem is that the records from this reservations table get not fetched.
Running this SQL statement in MySql Workbench gets the desired resultset:
SELECT * FROM xPois pp
LEFT JOIN pois p ON pp.poiId = p.poiId
LEFT JOIN reservations r ON p.poiId = r.poiId
WHERE pp.xPoiId = '2011';
In Eclipse I can see {IndirectList: not instantiated} when I inspect the xDao instance in debug mode.
How can I get the records from this table being stored in the PoiDao using JPA?
public abstract class PoiDao implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="poiId")
private Integer poiId;
#OneToOne(optional=false, cascade=CascadeType.ALL)
#JoinColumn(name="addressId",insertable=true,
updatable=true, unique=true, nullable=false)
private AddressDao address;
#Embedded
private GeoLocationDao geoLocation;
#Convert("poiTypeConverter")
private ServiceTypeEnum poiType;
#Column(name="operator")
private String operator;
#Column(name="reservable")
private boolean reservable;
#OneToMany(orphanRemoval=true, cascade=CascadeType.ALL, fetch=FetchType.LAZY)
#JoinColumn(name="poiId", insertable=true, updatable=true)
private List<ReservationDao> existingReservations;
...
}
#Entity
#Table(name="xPois")
#DiscriminatorValue("X")
#NamedQueries({
#NamedQuery(name="XPoiDao.findAll", query="SELECT p FROM XPoiDao p"),
#NamedQuery(name="XPoiDao.findByXPoiId",
query="SELECT pp FROM XPoiDao pp LEFT JOIN PoiDao p ON pp.poiId = p.poiId "
+ "LEFT JOIN ReservationDao r ON p.poiId = r.poiId WHERE pp.xPoiId = :xPoiId")
})
#ObjectTypeConverters({
#ObjectTypeConverter (
name="xPoiStatusConverter",
dataType=java.lang.String.class, // type in DB
objectType=XPoiStatusEnum.class, // Java type
conversionValues={
#ConversionValue(dataValue="FREE", objectValue="FREE"),
#ConversionValue(dataValue="OCCUPIED BY VALUE", objectValue="OCCUPIED_BY_VALUE"),
#ConversionValue(dataValue="OCCUPIED MANUALLY", objectValue="OCCUPIED_MANUALLY"),
#ConversionValue(dataValue="BLOCKED", objectValue="BLOCKED")
}
)
})
public class XPoiDao extends PoiDao implements Serializable {
/**
*
*/
private static final long serialVersionUID = 2496267921294255723L;
// #Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private Integer id;
#Column(name="xPoiId")
private String xPoiId;
#Convert("xPoiStatusConverter")
#Column(name="status")
private XPoiStatusEnum status;
#Embedded
private ContactDao contact;
// #OneToMany(orphanRemoval=true, cascade=CascadeType.ALL, fetch=FetchType.LAZY)
// #JoinColumn(name="poiId",insertable=true,updatable=true)
// private List<ReservationDao> existingReservations;
#OneToMany(orphanRemoval=true, cascade=CascadeType.ALL, fetch=FetchType.LAZY)
#JoinColumn(name="parkingPoiId",insertable=true,updatable=true)
private List<OperatingHourDao> operatingHours;
...
}
You've got FetchType.LAZY in there. Do you get an empty list when you try to access it? Debuggers might not trigger the fetch requests.