JPA nested inner join query - java

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.

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

RowMapper strange result

I am working on a Spring application that use JdbcTemplate to query the database, and the result from the rowmapper is different from the result of the query.
My query returns :
+--------------------+---------+------+------+----------+----------+----------+----------+---------+----------------+-----------------+
| ORIGINATING_SYSTEM | SYS_REF | CUR1 | CUR2 | TRADE_DT | START_DT | END_DT | BOOK_REF | BOOK_ID | NOMINAL | ORIGIN_VALUE_DT |
+--------------------+---------+------+------+----------+----------+----------+----------+---------+----------------+-----------------+
| CC | 4000000 | USD | | 01/04/19 | 01/04/19 | 01/04/19 | TDCZK | 317 | -8245872154,55 | 29/03/19 |
| GPS | 4000000 | EUR | | 01/04/19 | 28/03/19 | 28/03/19 | TDCZK | 317 | 55555550 | |
+--------------------+---------+------+------+----------+----------+----------+----------+---------+----------------+-----------------+
This result is processed with a rowmapper to get a list :
public List<Trade> findBackValueTrades() {
List<Trade> trades = getJdbcTemplate().query(FIND_BACK_VALUE_TRADES, new BackValueTradeMapper());
}
class BackValueTradeMapper implements RowMapper<Trade> {
public BackValueTradeMapper() {
}
#Override
public Trade mapRow(final ResultSet rs, final int rowNum) throws SQLException {
Trade trade = new Trade();
trade.setOriginatingSystem(rs.getString("ORIGINATING_SYSTEM"));
trade.setSystemRef(rs.getString("SYS_REF"));
trade.setNominal(rs.getDouble("NOMINAL"));
Currency cur1 = new Currency();
cur1.setId(rs.getString("CUR1"));
trade.setCurrency1(cur1);
Currency cur2 = new Currency();
cur2.setId(rs.getString("CUR2"));
trade.setCurrency2(cur2);
trade.setTradeDate(rs.getDate("TRADE_DT"));
trade.setStartDate(rs.getDate("START_DT"));
trade.setEndDate(rs.getDate("END_DT"));
Book book = new Book();
book.setBookRef(rs.getString("BOOK_REF"));
book.setId(rs.getLong("BOOK_ID"));
trade.setBook(book);
trade.setNominal(rs.getDouble("NOMINAL"));
trade.setEnteredDate(rs.getDate("ORIGIN_VALUE_DT"));
return trade;
}
}
where Trade is just an #Entity, containing only fields and getters and setters :
#Table(name = "TRADES")
#Entity
public class Trade implements Serializable {
private static final long serialVersionUID = 2143115773381859155L;
#Column(name = "ID")
#Id
private Long id;
#Column(name = "SYS_REF")
private String systemRef;
#Column(name = "ORIGINATING_SYSTEM")
private String originatingSystem;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CUR1")
private Currency cur1;
// remaining fields + getters/setters
}
The problem is in the resulting list, where it seems the trade from CC was overwritten :
+--------------------+---------+------+------+----------+----------+----------+----------+---------+-----------+-----------------+
| ORIGINATING_SYSTEM | SYS_REF | CUR1 | CUR2 | TRADE_DT | START_DT | END_DT | BOOK_REF | BOOK_ID | NOMINAL | ORIGIN_VALUE_DT |
+--------------------+---------+------+------+----------+----------+----------+----------+---------+-----------+-----------------+
| GPS | 4000000 | EUR | | 01/04/19 | 28/03/19 | 28/03/19 | TDCZK | 317 | 55555550 | 29/03/19 |
| GPS | 4000000 | EUR | | 01/04/19 | 28/03/19 | 28/03/19 | TDCZK | 317 | 55555550 | 28/03/19 |
+--------------------+---------+------+------+----------+----------+----------+----------+---------+-----------+-----------------+
Why is that ?
By the way, I was able to resolve the issue by extending the Trade class and overriding equals and hashCode, but I want to know why it worked.

Query Predicate in QueryDSL

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

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?

Categories