Define an entity via a custom SQL query in Hibernate 5.1 - java

I'm working with a non normalized 3rd party database meaning I cannot change the schema. I'm trying to map the tables to JPA entities using Hibernate 5.1
There are 2 simple tables A and B:
| A_ID(pk) | | B_ID(pk) |
------------- -------------
| 1 | | 1 |
------------- | 2 |
-------------
Table C has a composite primary key and has a Many-To-One relation to Table A:
| A_ID(pk&fk) | QUANTITY(pk) | VALID_FROM(pk) |
---------------------------------------------------
| 1 | 1 | 2017-05-21 |
| 1 | 1 | 2018-01-01 |
| 1 | 2 | 2017-05-21 |
Table D has a composite primary key:
| A_ID(pk&fk) | QUANTITY(pk) | VALID_FROM(pk) | B_ID(pk&fk) |
--------------------------------------------------------------------
| 1 | 1 | 2018-01-21 | 1 |
| 1 | 2 | 2018-01-21 | 1 |
| 1 | 2 | 2018-05-01 | 2 |
the VALID_FROM column is not part of the join condition between the tables and can take up any value.
I'm trying to set up a relation between Table C and D but because of the VALID_FORM primary key component they cannot be modelled with Many-To-One. And since there is no join table they cannot be modelled with Many-To-Many either.
The best solution would be to create a view like
CREATE VIEW C_NORM AS
SELECT DISTINCT A_ID, QUANTITY
FROM TABLE_C;
which would produce view C_NORM:
| A_ID(pk&fk) | QUANTITY(pk) |
----------------------------------
| 1 | 1 |
| 1 | 2 |
Creating the C_NORM entity on this view could have
a One-To-Many relation with Table C
and another One-To-Many relation with Table D
but I cannot change the schema thus I cannot create a new view.
Is there any way to define an entity as a class with annotations that is basically based on a native SQL query rather than a view or table in the DB?

No that's not possible and it doesn't make sense.
Entities are for update, insert and delete. If you don't want to do any of these operations you shouldn't use entities.
You can use the #SqlResultSetMapping to map a result of a native query to a class
Query q = em.createNativeQuery(
"SELECT c.id, c.name, COUNT(o) as orderCount, AVG(o.price) AS avgOrder " +
"FROM Customer c " +
"JOIN Orders o ON o.cid = c.id " +
"GROUP BY c.id, c.name",
"CustomerDetailsResult");
#SqlResultSetMapping(name="CustomerDetailsResult",
classes={
#ConstructorResult(targetClass=com.acme.CustomerDetails.class,
columns={
#ColumnResult(name="id"),
#ColumnResult(name="name"),
#ColumnResult(name="orderCount"),
#ColumnResult(name="avgOrder", type=Double.class)})
})
Or alternatively use QLRM: https://github.com/simasch/qlrm

Related

Spring JPA OneToMany with composite Key mapping

I am having issues mapping the following using spring JPA. Let's say I have a order table which has two primary keys. one is a foreign key to customer and another is a foreign key to order Type as seen below:
Customer
+----+------+--+
| id*| name | |
+----+------+--+
| 1 | Joe | |
+----+------+--+
Order
+------------+-------------+
| customerId | orderTypeId |
+------------+-------------+
| 1 | 1 |
+------------+-------------+
OrderType
+----+--------+
| id | type |
+----+--------+
| 1 | online |
+----+--------+
the idea is each customer has a one to many relationship with Orders. the primary key of orders is a combination of the two foreign keys.
any help would be appreciated.

How to ensure that a ResultSet includes rows for "missing" observations

I have a ResultSet table shown below:
+------------+--------------------+--------------------+---------+-----------------------+
| test_date | upload_kbps | download_kbps | latency | network_operator_name |
+------------+--------------------+--------------------+---------+-----------------------+
| 2017-04-02 | 19.12741903076923 | 44.614721153846155 | 32.1250 | Alcatel |
| 2017-03-31 | 18.30683616557377 | 44.294387978142076 | 34.7432 | Alcatel |
| 2017-03-31 | 20.643555595555555 | 50.99801587301587 | 32.1640 | Vodafone |
I want to modify the ResultSet for further use where while I add a row into the ResultSet like so:
+------------+--------------------+--------------------+---------+-----------------------+
| test_date | upload_kbps | download_kbps | latency | network_operator_name |
+------------+--------------------+--------------------+---------+-----------------------+
| 2017-04-02 | 19.12741903076923 | 44.614721153846155 | 32.1250 | Alcatel |
| 2017-04-02 | 0 | 0 | 0 | Vodafone |
| 2017-03-31 | 18.30683616557377 | 44.294387978142076 | 34.7432 | Alcatel |
| 2017-03-31 | 20.643555595555555 | 50.99801587301587 | 32.1640 | Vodafone |
The logic behind this is to basically add a null row for that telecom where on that day, a speedtest was not done for it. For further clarification: the reason i need to do this is because the table in MySQL db does not record a row/entry for tests not done, hence the lack of a row in my original ResultSet, hence the need for me to add a 'NULL/0' row to reflect the lack of test for that telco, on that day. I don't have direct access to that database to modify the entries currently so this was the best I can think of.
Any idea how I can do this? Appreciate the help!
It sounds like you want to add rows to the ResultSet after the fact. AFAIK, we can't do that. Instead we need to construct our SQL query so that it will produce the "extra" rows we need.
So if we have a table named "test" and
SELECT * FROM test
ORDER BY test_date DESC, network_operator_name
produces
test_date upload_kbps download_kbps latency network_operator_name
---------- ---------------- ---------------- ------- ---------------------
2017-04-02 19.1274190307692 44.6147211538461 32.125 Alcatel
2017-03-31 18.3068361655737 44.294387978142 34.7432 Alcatel
2017-03-31 20.6435555955555 50.9980158730158 32.164 Vodafone
then we can start with a query to produce a row for every combination of test_date and network_operator_name
SELECT test_date, network_operator_name
FROM
(SELECT DISTINCT network_operator_name FROM test) unique_operators
CROSS JOIN
(SELECT DISTINCT test_date FROM test) unique_dates
which gives us
test_date network_operator_name
---------- ---------------------
2017-03-31 Alcatel
2017-03-31 Vodafone
2017-04-02 Alcatel
2017-04-02 Vodafone
Then we can LEFT JOIN that query with the actual table
SELECT
required_rows.test_date,
COALESCE(test.upload_kbps, 0) AS upload_kbps,
COALESCE(test.download_kbps, 0) AS download_kbps,
COALESCE(test.latency, 0) AS latency,
required_rows.network_operator_name
FROM
(
SELECT test_date, network_operator_name
FROM
(SELECT DISTINCT network_operator_name FROM test) unique_operators
CROSS JOIN
(SELECT DISTINCT test_date FROM test) unique_dates
) required_rows
LEFT JOIN
test
ON required_rows.test_date = test.test_date
AND required_rows.network_operator_name = test.network_operator_name
ORDER BY required_rows.test_date DESC, required_rows.network_operator_name
producing
test_date upload_kbps download_kbps latency network_operator_name
---------- ---------------- ---------------- ------- ---------------------
2017-04-02 19.1274190307692 44.6147211538461 32.125 Alcatel
2017-04-02 0 0 0 Vodafone
2017-03-31 18.3068361655737 44.294387978142 34.7432 Alcatel
2017-03-31 20.6435555955555 50.9980158730158 32.164 Vodafone
You can use NULLIF() function of mysql. In the function you provide the variable that you test for whether speedtest was done or not. Suppose, latency would have zero when speedtest is not done on that day.
Then NULLIF(latency,0) would be the value for the column latency in your insert command. And so for the other columns you need to fill with NULL for certain conditions. This function returns NULL if the 1st argument matches with second argument. Otherwise gives the actual value of the 1st argument.

Join using Criteria API without foreign constraint

I'm really new to the Criteria API and I do not know how I can create a join query for the following situation. I already looked into the Oracle documentation of the Criteria API, but I could not make the examples work.
Say you have the following two tables in your database.
Item Export
---------------------------- ---------------------------
| ItemId | DateUpdated | | ItemId | ExportDate |
---------------------------- ---------------------------
| 1 | 02/02/2016 | | 1 | 02/02/2016 |
---------------------------- ---------------------------
| 2 | 03/02/2016 | | 2 | 03/02/2016 |
---------------------------- ---------------------------
| 3 | 06/02/2016 | | 3 | 05/02/2016 |
---------------------------- ---------------------------
| 4 | 07/02/2016 |
----------------------------
The corresponding entity classes are exact representations of the tables.
The query should join Item with Export, but there is no foreign key from Export.ItemId to Item.ItemId. Further, as a result the query should select Item with ItemId 3, because Export.ExportDate is before Item.DateAdded, and Item with ItemId 4, because the id is not in Export.
How can I do that?

How do I selectively update columns in a table when using LOAD DATA INFILE?

I am trying to load data from a text file into a MySQL table, by calling MySQL's LOAD DATA INFILE from a Java process. This file can contain some data for the current date and also for previous days. The table can also contain data for previous dates. The problem is that some of the columns in the file for previous dates might have changed. But I don't want to update all of these columns but only want the latest values for some of the columns.
Example,
Table
+----+-------------+------+------+------+
| id | report_date | val1 | val2 | val3 |
+----+-------------+------+------+------+
| 1 | 2012-12-01 | 10 | 1 | 1 |
| 2 | 2012-12-02 | 20 | 2 | 2 |
| 3 | 2012-12-03 | 30 | 3 | 3 |
+----+-------------+------+------+------+
Data in Input file:
1|2012-12-01|10|1|1
2|2012-12-02|40|4|4
3|2012-12-03|40|4|4
4|2012-12-04|40|4|4
5|2012-12-05|50|5|5
Table after the load should look like
mysql> select * from load_infile_tests;
+----+-------------+------+------+------+
| id | report_date | val1 | val2 | val3 |
+----+-------------+------+------+------+
| 1 | 2012-12-01 | 10 | 1 | 1 |
| 2 | 2012-12-02 | 40 | 4 | 2 |
| 3 | 2012-12-03 | 40 | 4 | 3 |
| 4 | 2012-12-04 | 40 | 4 | 4 |
| 5 | 2012-12-05 | 50 | 5 | 5 |
+----+-------------+------+------+------+
5 rows in set (0.00 sec)
Note that column val3 values are not updated. Also I need to do this for large files as well, some files can be >300Megs or more, and so it needs to be a scalable solution.
Thanks,
Anirudha
It would be good to use LOAD DATA INFILE with REPLACE option, but in this case records will be dropped and added again, so old val3 values will be lost.
Try to load data into temporary table, then update your table from temp. table using INSERT ... SELECT/UPDATE or INSERT ... ON DUPLICATE KEY UPDATE statements.

Criteria joining two tables using more than one parameter

I have two tables which are related:
+-----------+ +----------+
| First | * 1 | Second |
+-----------+ --------- +----------+
| fk_Second | | id |
| version | | ... |
| ... | | y |
| x | +----------+
+-----------+
Hibernate has a ManyToOne definition from First to Second. The {fk_Second, version} is a composite-id of the First table (although I don't think it's relevant in this case).
I am trying to write Criteria call, which in SQL would look like as:
SELECT * FROM First WHERE
First.fk_Second = Second.id AND
First.x = Second.y
I'm finding trouble in generating the last bit - the extra join condition.
Criteria c = session.createCriteria(First.class);
.createCriteria("second", "join_between_first_and_second")
.add(Restrictions.eqProperty("y", "x") // <- fails: "x" is not found
I can not use HQL queries in this situation. Is there any way writing this differently? Can this be written avoiding subquery/DetachedCriteria?
Criteria c = session.createCriteria(First.class, "first");
c.createAlias("first.second", "theSecond");
c.add(Restrictions.eqProperty("first.x", "theSecond.y");
If you don't prepend an alias to your property, the property is considered part of the root entity of the criteria (First in this case).
Try HQL 'With' clause..
SELECT f.* FROM First f left join Second s ON f.fk_Second = s.id with f.x = s.y;

Categories