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;
}
}
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
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
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?
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();