How to do a Hibernate nested select? - java

Not sure if I worded the question correctly but i'm working with a postgres database
I'm looking for the equivalent of the following sql statement.
Select * from track where id in (Select track_id from track_item where item_time between x and y).
I'm new to hibernate and i've tried doing this using a Hibernate Query and using Criteria and failed both ways.
If there is an awnser using both methods it would be nice to see both examples as I'm trying to learn how to use both.
This is the create table information for the two tables
CREATE TABLE track (
id bigint NOT NULL,
track_uuid text,
track_number text,
track_exercise_indicator_id bigint NOT NULL,
track_simulation_indicator_id bigint NOT NULL,
track_status_id bigint,
last_modified timestamp with time zone DEFAULT timezone('utc'::text, now())
);
CREATE TABLE track_item (
id bigint NOT NULL,
track_item_type_id bigint NOT NULL,
item_time bigint NOT NULL,
frame_number bigint,
image_source text,
last_modified timestamp with time zone DEFAULT timezone('utc'::text, now()),
track_item_uuid character varying(200) NOT NULL,
track_id bigint NOT NULL
);
ALTER TABLE ONLY track_item
ADD CONSTRAINT track_item_track_id_fkey FOREIGN KEY (track_id) REFERENCES track(id);
ALTER TABLE ONLY track_item
ADD CONSTRAINT track_item_track_item_type_id_fkey FOREIGN KEY (track_item_type_id) REFERENCES track_item_type(id);
The java classes generated from the above table are.
#Entity
#Table(name = "track", schema = "d2d")
public class Track implements java.io.Serializable {
/**
* ID.
*/
private static final long serialVersionUID = 2313376497269789747L;
private long id;
private TrackSimulationIndicator trackSimulationIndicator;
private TrackStatus trackStatus;
private TrackExerciseIndicator trackExerciseIndicator;
private String trackUuid;
private String trackNumber;
private Date lastModified;
private Set<TrackMessageToTrackMapping> trackMessageToTrackMappings = new HashSet<TrackMessageToTrackMapping>(
0);
private Set<TrackInterval> trackIntervals = new HashSet<TrackInterval>(0);
private Set<AnomalyToTrackMapping> anomalyToTrackMappings = new HashSet<AnomalyToTrackMapping>(
0);
}
#Entity
#Table(name = "track_item", schema = "d2d")
public class TrackItem implements java.io.Serializable {
/**
* ID.
*/
private static final long serialVersionUID = 8469898950069782997L;
private long id;
private TrackItemType trackItemType;
private long itemTime;
private Long frameNumber;
private String imageSource;
private String trackItemUUID;
private Track track;
private Date lastModified;
private Set<TrackPoint> trackPoints = new HashSet<TrackPoint>(0);
private Set<TrackInformation> trackInformations = new HashSet<TrackInformation>(
0);
}

Your query should use joins to be more efficient, and be more easily translatable in HQL:
select distinct t.* from track t
inner join track_item item on t.id = item.track_id
where item.item_time between 100 and 200
This SQL query would translate to HQL as:
select distinct t from Track t
inner join t.items item
where item.time between 100 and 200
And in Criteria:
Criteria c = session.createCriteria(Track.class, "t");
c.setResultTransformer(criteria.DISTINCT_ROOT_ENTITY);
c.createAlias("t.items", "item");
c.add(Restrictions.between("item.time", 100L, 200L);
EDIT:
you've mapped the association as a ManyToOne association from TrackItem to Track. The inverse OneToMany association from Track to TrackItem doesn't exist, so the above queries won't work. The following HQL would work though:
select distinct t from Item item
inner join item.track t
where item.time between 100 and 200
You can't translate such a query to Criteria though, so you'd better make the association bidirectional: there's no reason for a track to not know about its items.

A colleague of mine came up with this Criteria solution below which works. The Solution JB Nizet posted for HQL statement works once he updates the table name and the reference to itemTime. The HQL query is posted at the bottom of this also.
/**
* #param p_startTime The date and time in ms
* #param p_endTime The date and time in ms
* #param p_session the session
* #return Returns the latest valid rule for the specified date time
* #throws IOException
*/
#SuppressWarnings("unchecked")
public static List<Track> loadAllTracksByDateRange(final long p_startTime,
final long p_endTime, final Session p_session) throws IOException {
// create the criteria
final Criteria criteria = p_session.createCriteria(TrackItem.class);
// set projection
criteria.setProjection(Projections.distinct(Projections
.property("track")));
// add the restriction
criteria.add(Restrictions.ge("itemTime", p_startTime));
criteria.add(Restrictions.le("itemTime", p_endTime));
return criteria.list();
}
HQL Solution:
Select distinct t
from TrackItem item inner join item.track t
where item.itemTime between 100L and 200L;

You can do nested selects with the DetachedCriteria class:
Session s = obtainSession();
Criteria c = s.createCriteria(Track.class);
DetachedCriteria dc = DetachedCriteria.forClass(TrackItem.class);
dc.setProjection(Property.forName("track"));
dc.add(Restrictions.between("itemTime", x, y));
c.add(Subqueries.propertyIn("id", dc))
Look at questions
diff between criteria and detached criteria in hibernate? and
when to use detached criteria in hibernate?
and hibernate documentation Chapter 15.8
for details

Related

Generate Hibernate Id from database function, for table partitioning

I want to embed date information in the primary key, for a table that will be partitioned (monthly) in a PostgreSQL database. This should in theory speed up the process on finding out in which partition to look for the data. I followed this article to embed the date in a date into the serial.
Now, I am however facing the problem that I can't get the Id been used by Hibernate.
c.f. the sql that should give an idea of the attempted approach.
CREATE SEQUENCE test_serial START 1;
CREATE OR REPLACE FUNCTION gen_test_key() RETURNS BIGINT AS $$
DECLARE
new_id bigint;
BEGIN
new_id = (nextval('public.test_serial'::regclass)::bigint % 10000000000000::bigint
+ ( (EXTRACT(year from now())-2000)::bigint * 10000::bigint
+ EXTRACT(month from now())::bigint * 100::bigint
+ EXTRACT(day from now())::bigint
)::bigint * 10000000000000::bigint
)::bigint;
RETURN new_id;
END;
$$ LANGUAGE plpgsql;
CREATE TABLE test
( id bigint primary key default gen_test_key(),
something text,
tstamp timestamp default now()
) PARTITION BY RANGE (id);
CREATE TABLE test_2022_10 PARTITION OF test
FOR VALUES FROM (2210100000000000000::bigint ) TO (2211010000000000000::bigint);
I came across a similar question, where it was suggested to use a stored procedure. Unfortunately only functions are allowed as default in the table definition and therefore stored procedures, seam not to work for me.
I think what you need here is a subtype of SequenceStyleGenerator that overrides determineBulkInsertionIdentifierGenerationSelectFragment to run the code of this function. You should be able to configure this generator on your entity with #GenericGenerator. I understand the desire to use this concept when you don't want to change your existing queries, but are you sure that partitioning will help you in your use case?
Also, be careful and do not rely on the date information in the primary key, because with pooled optimizers, it might happen that a value is generated way before it actually is used as primary key for a row.
So this is a solution that worked out in the end as suggested #ChristianBeikov here the entity with the annotations pointing to the CustomIdGenerator.
public class Test {
#Id
#GenericGenerator(name = "CustomIdGenerator", strategy = "nl.test.components.CustomIdGenerator")
#GeneratedValue(generator = "CustomIdGenerator")
private Long id;
private String something;
private OffsetDateTime tstamp;
}
As explained by #Mr_Thorynque it is similarly possible to call a stored function as a procedure. Just replace "CALL gen_test_key()" with "SELECT gen_test_key()" and don't pass it to the wrong method for stored procedures connection.prepareCall(CALL_STORE_PROC);, but instead connection.prepareStatement(STORED_FUNCTION); So, this is the CustomIdGenerator.
public class CustomIdGenerator implements IdentifierGenerator {
private static final String STORED_FUNCTION = "select gen_test_key()";
#Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
Long result = null;
try {
Connection connection = session.connection();
PreparedStatement pstmt = connection.prepareStatement(STORED_FUNCTION);
ResultSet resultSet = pstmt.executeQuery();
if (resultSet.next()) {
result = resultSet.getLong(1);
System.out.println("Generated Id: " + result);
}
} catch (SQLException sqlException) {
throw new HibernateException(sqlException);
}
return result;
}
}

DataException: could not execute query

I get this error when I try to run start my application:
org.springframework.dao.DataIntegrityViolationException: could not execute query; SQL [SELECT * FROM testquestions ORDER by id DESC LIMIT 1]; nested exception is org.hibernate.exception.DataException: could not execute query
As seen in previous problems on StackOverflow, I tried to adjust the length of my data input in my sql file and I've set the length of my #Column to the same amount of characters. this didn't help.
this is my #Table class:
#Entity
#Getter
#Setter
#Table(name = "testquestions")
public class TestQuestion {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "questiontitle", length = 2000)
private String questionTitle;
#Column(name = "info", length = 4096)
private String Info;
#Column(name = "solvetime")
private int solveTime;
#Column(name = "difficultylevel")
private DifficultyLevel difficultyLevel;
#Column(name = "questionimage")
private Image questionImage;
public TestQuestion(){
}
public TestQuestion(int id, String questionTitle, String info, DifficultyLevel difficultyLevel) {
this.id = id;
this.questionTitle = questionTitle;
Info = info;
this.difficultyLevel = difficultyLevel;
}
public String getInfo() {
return Info;
}
}
This is my # Query in my QuestionRepository:
#Query(value = "SELECT * FROM testquestions ORDER by id DESC LIMIT 1", nativeQuery = true)
TestQuestion fetchLastQuestion();
This is my database.sql file, it writes to a PostgreSQL data base:
TRUNCATE TABLE users CASCADE;
TRUNCATE TABLE testquestions CASCADE;
DROP TABLE users;
DROP TABLE testquestions;
CREATE TABLE users(
id int,
username varchar(255),
password varchar(255),
role varchar(255)
);
CREATE TABLE testquestions(
id int primary key ,
questiontitle varchar(2000),
info varchar(4096),
solvetime int,
difficultylevel varchar(255),
questionimage bytea
);
INSERT INTO users(id, username, password, role)
VALUES (0, 'user', 'u', 'user'),
(1, 'user','u','user');
INSERT INTO testquestions(id,questiontitle, info, solvetime, difficultylevel, questionimage)
VALUES (0, 'Multiple Databases', 'A company wants to use Spring Boot in a web application which should use JPA as a database abstraction. The unit tests should be run on an H2 database while the production should run on a MySQL database.
Select all the things that need to be done or that will be done automatically by Spring Boot.', 3, 'Easy',
''),
(1, 'Screen Orientation', 'Which of these methods are called when the screen changes orientation from portrait to landscape in Android?',
3, 'Easy',''),
(2, 'Merge Names', 'Implement the uniqueNames method. When passed two arrays of names, it will return an array containing the names that appear in either or both arrays. The returned array should have no duplicates.
For example, calling MergeNames.uniqueNames(new String[]{''Ava'', ''Emma'', ''Olivia''}, new String[]{''Olivia'', ''Sophia'', ''Emma''}) should return an array containing Ava, Emma, Olivia, and Sophia in any order.',
10, 'Easy',''),
(3, 'Date', 'Write a function that converts user entered date formatted as M/D/YYYY to a format required by an API (YYYYMMDD). The parameter "userDate" and the return value are strings.
For example, it should convert user entered date "12/31/2014" to "20141231" suitable for the API.', 10, 'Easy', ''),
(4, 'Inspector', 'Fix the bugs in the following HTML code.', 10, 'Easy',''),
(5, 'Train Composition', 'A TrainComposition is built by attaching and detaching wagons from the left and the right sides, efficiently with respect to time used.
For example, if we start by attaching wagon 7 from the left followed by attaching wagon 13, again from the left, we get a composition of two wagons (13 and 7 from left to right). Now the first wagon that can be detached from the right is 7 and the first that can be detached from the left is 13.
Implement a TrainComposition that models this problem.', 20, 'Hard', '');
Has anyone got an idea how to fix this error?
Thanks!
Tom

JPA CriteriaBuilder left outer join for table with no relations?

Initially I had requirement to write code using JPA CriteraiBuilder for following SQL:
SELECT ve.col_1,
(SELECT vm.col_4
FROM table2 vm
WHERE vm.col_2 = ve.col_2
AND vm.col_3 = ve.col_3
) as col_a
FROM table1 ve;
But I learnt that, it is not possible to add subquery in select clause. So I changed my query to use left outer join like this.
SELECT ve.col_1,
vm.col_4 as col_a
FROM table1 ve,
table2 vm
WHERE
vm.col_2 (+) = ve.col_2
AND vm.col_3 (+) = ve.col_3;
Now table1 and table2 do not have direct relations using foreign keys. Corresponding JPA entities look like:
Table1.java ->
#Column(name = "COL_1")
private String col_1;
#Column(name = "COL_2")
private String col_2;
#Column(name = "COL_3")
private String col_3;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name="COL_2"),
#JoinColumn(name="COL_3")
})
private Table2 table2;
Table2.java ->
#Column(name = "COL_4")
private String col_4;
#Column(name = "COL_2")
private String col_2;
#Column(name = "COL_3")
private String col_3;
My code looks like:
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final CriteriaQuery<SearchTO> query = criteriaBuilder.createQuery(
SearchTO.class);
Root<Table1> root = query.from(Table1.class);
final Join<Table1, Table2> joinTable2 = root.join(Table1_.table2,
JoinType.LEFT);
Then I am trying to fetch value using:
joinTable2.get(Table2_.col_4)
Then now I am getting error as:
A Foreign key refering com.Table2 from com.Table1 has the wrong number of column
Table2 has around 6 columns with annotation #Id and I can not change change it to have only two columns with #Id annotation.
Please let me know:
If it is possible to write code using CriteriaBuilder for my approach 1 (subquery in select clause).
If thats not possible, how can I implement this left outer join as mentioned for approach 2. Please note that Table2 does not have any references of Table1.
Please note that I am using plain JPA APIs. DB is Oracle11g. JDK version is 1.7.
One solution is to create view and query against it using criteria builder.
You can see my answer in Joining tables without relation using JPA criteria
Only change you need to do is use left join in your view definition.
Hope it solves your use case.
For Approach 1: You can write subquery for criteria query
Subquery<Entity1> subquery = cq.subquery( Entity1.class );
Root fromSubQuery = subquery.from( Entity1.class );
subquery.select( cb.max( fromSubQuery.get( "startDate" ) ) );
subquery.where( cb.equal( fromSubQuery.get( "xyzId" ), fromRootOfParentQuery.get( "xyzId" ) ) );
Use it as :
Root<Entity2> entity2 = cq.from( Entity2.class );
Predicate maxDatePredicate = cb.and( cb.equal( entyty2.get( "startDate" ), subquery ) );
For Approach 2:
There is no other way than having relationship between two entities for left join. You can define private variable for relationship without getter & setter and use that variable for setting left join.
Then add the predicate to criteriaBuilder

hibernate HQL delete query nullifies single column

I am working on spring hibernate application and trying to delete from a table using non-id many-to-one relationship based column.
Entity classes are:
#Entity
public class Day {
#id(name = "DAY_ID")
dayId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "DAY_ID")
List<Holiday> holidayList;
...
}
#Entity
public class Holiday {
#id(name="HOLIDAY_ID")
holidayId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DAY_ID")
Day day;
...
}
I am trying to delete a row from holiday table using hql.
String query = "DELETE FROM Holiday WHERE day.dayId = " + dayObject.getdayId();
Query holidayDeleteQuery = getSession().createQuery(query);
holidayDeleteQuery.executeUpdate();
In the console i am getting proper delete query but on checking DB found out that the row is still there but now the DAY_ID column in holiday table is null. I am not able to figure out why is this happening?
EDIT: help!!! My main problem is why DAY_ID column is changing to null value??
I'm not sure that this is your problem, but in your query you say "DELETE FROM Holidays ...", but your Class name is Holiday. In HQL you should be using Class names rather than table names or anything else. Is this typo in your code or just on here?
Actually after looking further there are a few more problems. This is how I'd write it:
String query = "DELETE FROM Holiday h WHERE h.day = :day";
Query holidayDeleteQuery = getSession().createQuery(query);
query.setParameter("day", dayObject);
holidayDeleteQuery.executeUpdate();
To break it down - use the Class name "Holiday", assign it an alias "h" then reference the day field of the Holiday object ("h.day") and compare it to the actual Day object you have.
What is your ONDELETE foreign key constrain? Might it that other part of your application inserting a row?

How to retrieve Google Appengine Objects by id (Long value)?

i have declared an entity the following way:
public class MyEntity {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
#Persistent
private String text;
//getters and setters
}
Now I want to retrieve the objects using the id. I tried to manage it from the Google Appengine Data Viewer with "SELECT * FROM MyEntity Where id = 382005" or via a query in a servlet. I get no results returned. But i know for sure that the object with the id exists (i made a jsp which queries all objects in the db and displays them in the db).
So what is wrong in my query? Am I querying the wrong field? The Google Appengine Data Viewer names the field "ID/name" and it has the value "id=382005". Do I have to query with this names? I've tried but it didn't work out :(
You can use below since you are querying using the primary key:
MyEntity yourEntity = entityManager.find(MyEntity.class, yourId);
Note, this should work as well, but it's easier to use find() if you are searching based on the primary key:
Query query = entityManager.createQuery(
"SELECT m FROM MyEntity m WHERE id = :id");
query.setParameter("id", yourId);
MyEntity yourEntity = (MyEntity) query.getSingleResult();

Categories

Resources