Query Predicate in QueryDSL - java

The environment is Java, Spring-boot, Hibernat, QueryDSL, MySQL.
I have table structure
Episode
+----+-------------+--------
| id | address_id | eventno
+----+-------------+--------
| 5 | 27 | F123
| 6 | 30 | F456
| 7 | 45 | F789
+----+-------------+--------
#Entity
public class Episode {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty
private String eventno;
#ManyToOne(cascade = CascadeType.ALL)
private Address address;
Episode_Person
+----+--------------+--------------+------------+-----------+
| id | episode_role | primary_flag | episode_id | person_id |
+----+--------------+--------------+------------+-----------+
| 19 | Buyer | | 5 | 1 |
| 20 | Subject | | 5 | 2 |
| 23 | Witness | | 6 | 3 |
| 24 | Child | | 6 | 4 |
| 27 | Buyer | | 5 | 3 |
| 63 | Investor | | 5 | 4 |
| 64 | Subject | | 7 | 1 |
| 65 | Subject | | 7 | 3 |
#Entity
public class EpisodePerson {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#Valid
private Person person;
#ManyToOne
private Episode episode;
Person
+----+-----------+----------+
| id | firstname | surname |
+----+-----------+----------+
| 1 | Clint | eastwood |
| 2 | Angelina | joilee |
| 3 | Brad | pitt |
| 4 | Jennifer | aniston |
#Entity
#Table(uniqueConstraints = #UniqueConstraint(columnNames = {"nia"}))
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String surname;
private String firstname;
private String gender;
So each episode has multiple people. And the join table is Episode_Person.
My UI has a datatable with a filter on each column:
The filtering already works on Event and Address. And looks like this predicate in QueryDSL:
BooleanBuilder where = new BooleanBuilder();
if (pagination.getFilterBy().getMapOfFilters().get("eventno")!=null) {
where.and(qEpisode.eventno.containsIgnoreCase(pagination.getFilterBy().getMapOfFilters().get("eventno")));
}
if (pagination.getFilterBy().getMapOfFilters().get("address")!=null) {
where.and(qEpisode.address.formattedAddress.containsIgnoreCase(pagination.getFilterBy().getMapOfFilters().get("address")));
}
where.and(qEpisode.creatingUser.eq(user));
List<Episode> e = episodeRepository.findAll(where);
How would I now add a 3rd predicate for case name where case name is constructed of the first two people returned in the collection of people against a episode?
UPDATE
For clarification the DTO thats backs the UI view contains the "casename" attribute. It is created in the service layer when Domain objects are converted to DTO:
episodeDashboard.setNames(episodePersonList.get(0).getPerson().getSurname().toUpperCase() +" & " +episodePersonList.get(1).getPerson().getSurname().toUpperCase());

Not easily unless you delegate some of the processing to the database.
If we can get the case_name property to be populated at the database tier rather than as a derived property in the application logic then the front-end code becomes trivial.
We can do this by means of a view. The exact definition of this will depend on your database however the output would be something like this:
episode_summary_vw
+------------+-------------------------+
| epsiode_id | case_name |
+------------+-------------------------+
| 5 | Eastwood & Joilee|
| 6 | Pitt & Aniston|
| 7 | Aniston & Pitt|
+------------+-------------------------+
For Oracle it looks like LISTAGG function is what you would want and for MySQL the GROUP_CONCAT functions. In MySQL then I think this would look something like:
CREATE VIEW episode_summary_vw as
SELECT ep.episode_id, GROUP_CONCAT(p.surname SEPARATOR ' & ')
FROM episode_person ep
INNER JOIN person p on p.id = ep.person_id
GROUP BY ep.episode_id;
-- todo: needs limit to first 2 records
Once we have a view then we can simply map the case_name to the Episode entity using the #SecondaryTable functionality of JPA:
#Entity
#Table(name = "episodes")
#SecondaryTable(name = "episode_summary_vw", primaryKeyJoinColumna = #PrimaryKeyJoinColumn(name="episode_id", reference_column_name="id"))
public class Episode {
#Column(name ="case_name", table = "episode_summary_vw")
private String caseName;
}
You then filter and sort on the property as for any other field:
if (pagination.getFilterBy().getMapOfFilters().get("caseName")!=null) {
where.and(qEpisode.caseName.containsIgnoreCase(pagination.getFilterBy().
getMapOfFilters().get("caseName")));
}

Related

Hibernate envers and ElementCollection

I have a simple program with a Resource and List of ItemSelection like this
#Audited
#Table(name = "resource_item")
public class Resource {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Access(AccessType.PROPERTY)
private Long id;
private String nom;
#ElementCollection(targetClass = ItemSelection.class)
#CollectionTable(name = "item_selection", joinColumns = #JoinColumn(name = "resource_id"))
#OrderColumn(name = "list_order")
private final List<ItemSelection> itemSelections = new ArrayList<>();
...
...
#Embeddable
#Access(AccessType.FIELD)
public class ItemSelection {
//#Column(name = "hierarchy_code", nullable = false)
private String hierarchyCode;
//#Column(name = "hierarchy_level", nullable = false)
private int hierarchyLevel;
//#Column(name = "hierarchy_value_code", nullable = false)
private String hierarchyValueCode;
...
...
When i m adding resource with 3 elements in itemSelections, hibernate envers log correctly:
localhost sa#envers=# select * from item_selection_aud ;
rev | revtype | resource_id | list_order | hierarchy_value_code | hierarchy_code | hierarchy_level
-----+---------+-------------+------------+----------------------+----------------+-----------------
2 | 0 | 1 | 1 | hvalueCode3 | hcode3 | 3
2 | 0 | 1 | 0 | hvalueCode1 | hcode1 | 1
2 | 0 | 1 | 2 | hvalueCode2 | hcode2 | 2
(3 rows)
when i m updating one line in item_selection, i only see one update in sql but hibernate envers log this:
localhost sa#envers=# select * from item_selection_aud ;
rev | revtype | resource_id | list_order | hierarchy_value_code | hierarchy_code | hierarchy_level
-----+---------+-------------+------------+----------------------+----------------+-----------------
2 | 0 | 1 | 1 | hvalueCode3 | hcode3 | 3
2 | 0 | 1 | 0 | hvalueCode1 | hcode1 | 1
2 | 0 | 1 | 2 | hvalueCode2 | hcode2 | 2
3 | 0 | 1 | 0 | hvalueCode1 | CODE | 1
3 | 2 | 1 | 0 | hvalueCode1 | hcode1 | 1
(5 rows)
as you can see, i have a revtype 2 and 0 for the modified line instead of revtype 1 for an update.
I'have try to use #Column(name = "hierarchy_code", nullable = false) but nothing change.
For now my only solution is to transform #ElementCollection into #OneToMany, but I have many #ElementCollection in my application so too many refactoring...
Did I miss something in the configuration? I am using DefaultAuditStrategy, same behavior with ValidityAuditStrategy that only add revend column. Hibernate-envers 5.6.12-Final

JPA nested inner join query

I have 3 tables and a ManyToMany relationship between usuario and projeto, and I'm having a headche to make it search accordly :
usuario
+-------------+-------------+
| idMatricula | nomeUsuario |
+-------------+-------------+
| 1 | Victor |
| 12345 | Juquinha |
| 2 | Nilton |
| 3 | Fatima |
| 4 | Bianca |
| 4422 | Pedrinho |
+-------------+-------------+
projeto
+-----------+----------------+--------------+
| idprojeto | nomeProjeto | faseProjeto |
+-----------+----------------+--------------+
| 1 | APS_6_semestre | Em Andamento |
| 2 | APS_7_semestre | Em testes |
| 3 | APS_8_semestre | Em testes |
| 4 | TCC | Concluido |
+-----------+----------------+--------------+
projeto_has_usuario
+-------------------+---------------------+
| projeto_idprojeto | usuario_idMatricula |
+-------------------+---------------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 2 |
+-------------------+---------------------+
I'm trying to query only the projects associated to the user, but my query doesnt work and I dont know if it is a semantic error or an annotation error. Here is my mapped class:
Projeto:
#Entity
#Table(name="projeto")
public class Projeto {
#GeneratedValue
#Id
#Column(name="idprojeto", nullable = false)
private int idprojeto;
#Column(name="nomeProjeto", nullable = false)
private String nomeProjeto;
#Column(name="faseProjeto", nullable = false)
private String faseProjeto;
#ManyToMany(mappedBy="projetos")
private List<Usuario> usuarios;
Usuario:
#Entity
#Table(name="usuario")
public class Usuario {
#Id
#Column(name="idMatricula", nullable = false)
private String id;
#Column(name="nomeUsuario", nullable = false)
private String nomeUsuario;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="projeto_has_usuario",
joinColumns={#JoinColumn(name="usuario_idMatricula")},
inverseJoinColumns={#JoinColumn(name="projeto_idprojeto")})
private List<Projeto> projetos;
Here is my query:
TypedQuery<Usuario> listaUsuario = manager.createQuery("SELECT u FROM Usuario as u", Usuario.class);
// Some validation ..
TypedQuery<Projeto> listaTabela = manager.createQuery("SELECT p FROM projeto p "
+ "JOIN p.usuarios u WHERE p.idprojeto MEMBER OF u.projetos", Projeto.class);
for (Projeto projetos : listaTabela.getResultList()) {
System.out.println("Nome: " + projetos.getNomeProjeto() + "\nID: " + projetos.getIdprojeto()
+ "\nFase: " + projetos.getFaseProjeto());
Returns me:
Nome: APS_6_semestre
ID: 1
Fase: Em Andamento
I'm using Eclipse IDE 4.7.2, Hibernate 5.2.12 and JPA 2.1.

Join two, three related tables by HQL

I have three related tables like below:
+-------------+---------+------------+
| customer_id | name | surname |
+-------------+---------+------------+
| 1 | Jan | Bielecki |
| 2 | Adam | Bielen |
.....
+----------+--------+---------------------+-------------+
| order_id | amount | date | customer_id |
+----------+--------+---------------------+-------------+
| 1 | 10.23 | 2017-02-15 00:00:00 | 1 |
| 2 | 20.56 | 2017-02-16 00:00:00 | 1 |
| 3 | 30.57 | 2017-02-17 00:00:00 | 2 |
| 4 | 40.52 | 2017-02-18 00:00:00 | 2 |
| 5 | 50.30 | 2017-02-19 00:00:00 | 1 |
.....
+-----------------+-----------+------------+----------+
| order_detail_id | item_name | item_price | order_id |
+-----------------+-----------+------------+----------+
| 1 | item 1 | 2.00 | 1 |
| 2 | item 2 | 2.50 | 1 |
| 3 | item 3 | 3.00 | 1 |
| 4 | item 4 | 4.00 | 2 |
| 5 | item 5 | 5.50 | 2 |
| 6 | item 6 | 7.60 | 3 |
| 7 | item 7 | 5.00 | 3 |
| 8 | item 8 | 3.00 | 4 |
| 9 | item 9 | 7.00 | 4 |
| 10 | item 10 | 8.00 | 4 |
| 11 | item 11 | 2.00 | 5 |
| 12 | item 12 | 2.50 | 5 |
.....
Firstly i'm fighting with connect first and second table. For connect surnames with sum of amount.
I'm trying like this:
select sum(o.amount) as totalSum
from Order as o,
Customer as c
join c.surname as surname
where c.orders:=o.customer
group by o.customer
order by sum(o.amount) desc
with changing to many ways this section: where c.orders:=o.customer
The most common error is NullPointerException.
Before done this in SQL:
Table customer_id <-> total_amount
SELECT customer_id,
SUM(amount) as total_amount,
COUNT(amount) as orders_quantity
FROM softhis_db.orders
GROUP BY customer_id;
Table customer_id <-> 3 most exp. orders + dates
SELECT orders.customer_id, orders.amount, orders.date
FROM orders_details
RIGHT JOIN orders
ON orders.order_id = orders_details.order_id
ORDER BY amount DESC
LIMIT 3;
Customer:
#Entity
#Table(name = "customers")
public class Customer {
#Id
#Column(name = "customer_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name", length = 50)
private String name;
#Column(name = "surname", length = 50)
private String surname;
#OneToMany(mappedBy = "customer")
private Set<Order> orders = new HashSet<>();
Order:
#Entity
#Table(name = "orders")
public class Order {
#Id
#Column(name = "order_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "date")
private Date date;
#Digits(integer = 5, fraction = 2)
#Column(name = "amount")
private BigDecimal amount;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer_id")
private Customer customer;
#OneToMany(mappedBy = "order")
private Set<OrderDetail> ordersDetails = new HashSet<>();
OrderDetail:
#Entity
#Table(name = "orders_details")
public class OrderDetail {
#Id
#Column(name = "order_detail_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Lon id;
#Column(name = "item_name", length = 50)
private String itemName;
#Digits(integer = 5, fraction = 2)
#Column(name = "item_price")
private BigDecimal itemPrice;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
private Order order;
The point is how to do this properly in HQL ? Next step will be searching by surname and getting result like 'my target'.
My target is:
+---------+---------------+
| surname | sum of amount |
+---------+---------------+
|Bielecki | 150.40 |
|Bielen | 130.34 |
......
+-----------------------------------+--------------------+
| surname | 3 most expensive orders | date |
+-----------------------------------+--------------------+
|Bielecki | 120.23 |2017-02-15 00:00:00 |
|Bielecki | 80.20 |2017-02-18 00:00:00 |
|Bielecki | 20.20 |2017-02-19 00:00:00 |
+---------+-------------------------+--------------------+
|Bielen | 190.23 |2017-02-15 00:00:00 |
|Bielen | 80.20 |2017-02-18 00:00:00 |
|Bielen | 20.20 |2017-02-19 00:00:00 |
+---------+-------------------------+--------------------+
.....
Try these queries
SELECT
customers.surname
, SUM(amount) "sum of amount"
FROM
customers
INNER JOIN
orders
ON
customers.customer_id = orders.customer_id
GROUP BY
customers.surname
ORDER BY
customers.surname ASC
For the 3 most expensive orders per surname you need to use user variables to create ranking.
And filter on that ranking.
SELECT
customers.surname
, orders_ranked.amount AS "3 most expensive orders"
, orders_ranked.date
FROM (
SELECT
*
, (
CASE
WHEN
#customer_id = orders.customer_id
THEN
#rank := #rank + 1
ELSE
#rank := 1
END
)
AS
rank
, #customer_id := orders.customer_id
FROM
orders
CROSS JOIN (
SELECT
#customer_id := 0
, #rank := 0
)
AS
init_user_variables
ORDER BY
orders.customer_id ASC
, orders.amount DESC
)
AS
orders_ranked
INNER JOIN
customers
ON
orders_ranked.customer_id = customers.customer_id
WHERE
orders_ranked.rank <= 3
I figured out how to translate those SQL queries to HQL.
In order:
1.
select o.customer.surname, sum(o.amount) as s from Order as o group by o.customer
2.
select o.customer.surname, o.amount, o.date from Order as o, OrderDetail as od

JPA Query on #OneToMany Set<Entity>

The GenericVehicle represent a vehicle which may have 0 or more GenericVehicleAccessory.
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class GenericVehicle extends GenericEntity {
#Id
private Long id;
#Column(nullable=false)
#NotNull(message="{GenericVehicle.vehicleName.notNull")
#Size(max=128, message="{GenericVehicle.vehicleName.size")
private String vehicleName;
#OneToMany(mappedBy = "genericVehicle", cascade = { CascadeType.ALL }, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<GenericVehicleAccessory> accessories;
// ..
}
#Entity
#Table(name = "GENERICVEHICLEACCESSORIES")
public class GenericVehicleAccessory extends GenericEntity {
#Id
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "GENERICVEHICLE_ID")
private GenericVehicle genericVehicle;
private String name;
// ..
}
I would like to perform a query which selects all GenericVehicle(s) which owns a Set (accessories) of a given criteria set.
For example:
+----+-------------------+
| ID | VEHICLENAME |
+----+-------------------+
| 1 | Toyota Auris |
| 2 | Volkswagen Passat |
| 3 | Bentley Arnage |
| 4 | Hyundai Accent |
| 5 | Toyota Auris |
+----+-------------------+
+-------------------+----------------------------------+
| GENERICVEHICLE_ID | NAME |
+-------------------+----------------------------------+
| 1 | Leather seats |
| 1 | Electronic stability control |
| 2 | Power steering |
| 4 | ABS |
| 4 | Airbag |
| 4 | Cruise control |
| 5 | Leather seats |
| 5 | Electronic stability control |
+-------------------+----------------------------------+
A criteria like this:
criteria.setVehicleName="Toyota Auris";
new GenericVehicleAccessory tmp0 = new GenericVehicleAccessory();
tmp0.setName="Leather Seats";
new GenericVehicleAccessory tmp1 = new GenericVehicleAccessory();
tmp1.setName="Electronic stability control";
criteria.addToAccessory(tmp0);
criteria.addToAccessory(tmp1);
would select the GenericVehicle entity with ID=1 (since ID=5 not have Electronic Stability Control).
How can I do that by using JPA Criteria API?

Hibernate annotation many-to-one issue

I have some issue with save object with one to many relationship. In my problem One UserGrop has many UserPermissions. Forthat relation ship I have create my domain class like this:
#Entity
#Table(name = "tbl_usergroup")
public class UserGroup implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="userGroupId")
private long userGroupId;
#Column(name = "userGroupName")
private String userGroupName;
#OneToMany(cascade=CascadeType.ALL,mappedBy="userGroup",fetch=FetchType.EAGER)
private Set<UserPermissions> userPermissions = new HashSet<UserPermissions>(0);
}
#Entity
#Table(name = "tbl_group_permissions")
public class UserPermissions implements Serializable {
#Id
#GeneratedValue
private Long userPermissionId;
#ManyToOne(cascade =CascadeType.ALL)
#JoinColumn(name="userGroupId",nullable=false)
#ForeignKey(name = "userGroupId")
private UserGroup userGroup;
}
But When saving UserGroup, it save hat UserPermisions Objects also. There with the table it does not have any relationship(when retrieving UserGroup object, it doesn't return Set of UserPermisions objects).
DB:
+------------------+-------------+
--+
| userPermissionId | userGroupId |
e |
+------------------+-------------+
--+
| 33 | NULL |
|
| 34 | NULL |
|
| 35 | NULL |
|
| 36 | NULL |
|
| 37 | NULL |
|
| 38 | NULL |
|
| 39 | NULL |
|
| 40 | NULL |
|
+------------------+-------------+
--+
8 rows in set (0.00 sec)
Can any body help me to solve this issue?
In the #OneToMany on UserGroup.userPermissions you have mappedBy="userGroup". This means the userGroup property in UserPermisions is responsible for the relation. I guess you don't set that property and upon saving it's still null.

Categories