Hibernate merge where clause - java

I have looked around for this answer, but I could not find anything that answered my question.
I am working on an application that uses Hibernate and it uses session.merge(object) to do the update or insert of the object. The insert works fine, but the update will fail due to a unique constraint on database fields (A, B, C) if multiple records exist with the same values for (A, B). The Hibernate model only has fields (A, B) defined as the id, it does not have (A, B, C) because when selecting or updating records only the record with a null value of C is wanted to be returned (C is a termination date where null means active and non-null means not active).
In the Hibernate model file (Table.hbm.xml), it has a where clause defined as follows:
<class name="..." table="..." lazy="true" batch-size="10" where="C is null">
That gets inserted when doing selects, but when doing the merge statement, the update statement does not have this as part of the where clause. The generated update is something like:
update table
set ...
where A=?
and B=?
That is fine, but what I would like is to also have the where clause from the Hibernate model file (the C is null clause) to be added to the where clause like it is for select statements.
Does anyone know what I can do to get that added to the update statement?
Thank you for your help.

I do not think it is possible to update the where clause in the update statement from the merge by using the where clause from the Hibernate model.
What I ended up doing to solve the problem was to do something like this:
void update(EntityName entity){
session.evict(entity);
Query update = session.createSQLQuery(
"update table_name "
+ "set c1 = ?,"
+ "c2 = ? "
+ "where A = ? "
+ "and B = ? "
+ "and C is null" //this was the line that was needing to be added
);
//set the parameter values
if(update.executeUpdate() == 0){
session.save(entity);
}
}
So I had to evict the entity to prevent any other updates from triggering an update later. Then an update was performed, and if no rows were updated, an insert is performed.

Related

HQL : The column must appear in GROUP BY or in Select

I've this query with WrappedBean :
buffer.append("SELECT new myPackage.WrappedBean(E.debitant.id, E.dateCalcul, E.verse, E.incidenceReprise, E.soldeSoumission) ");
buffer.append("FROM " + getEntityClassName() + " E ");
buffer.append("LEFT JOIN E.debitant DT ");
buffer.append("WHERE E.debitant.id = :idDebitant ");
buffer.append("AND YEAR(E.dateCalcul) = :year ");
buffer.append("GROUP BY E.debitant.id");
hqlQuery = session.createQuery(buffer.toString());
hqlQuery.setInteger("idDebitant", idDebitant);
hqlQuery.setInteger("year", year);
I've created WrappedBean for returning somme columns and for using Group BY.
When i try to execute it, i obtain this error :
org.postgresql.util.PSQLException: ERREUR: The column « complement0_.date_calcul » must appear in GROUP BY clause or must be used in Select (i translate the error from french)
My POSTGRES query doesnt contain date_calcul in Group BY.
Another problem, in my query i've also this :
SUM(CASE WHEN YEAR(dateCalcul)=#PECAnnee AND verse>0 THEN verse ELSE 0 END)
I know in HQL, we cant do case when in select, for this reason, i dont add SUM to column verse
What i've forgot ?
My POSTGRES query doesnt contain date_calcul in Group BY
That's the problem and what Postgres is complaining about. Why isn't it in the SQL query? Because it isn't in the HQL query. Any column that is selected without the use of some aggregate method like sum(), min(), max() etc. needs to be part of the GROUP BY clause since otherwise the DB doesn't know how to handle multiple values/conflicts.
As an example, what value of E.dateCalcul should be passed to WrappedBean if there are multiple debitors (debitants) (which is most probably the case since otherwise there wouldn't be any need for the GROUP BY clause)?
So to fix this either use
GROUP BY E.debitant.id, E.dateCalcul, E.verse, E.incidenceReprise, E.soldeSoumission
or use aggregate functions, e.g.
WrappedBean(E.debitant.id, max(E.dateCalcul), min(E.verse), max(E.incidenceReprise), sum(E.soldeSoumission))

JdbcTemplate select for update

I have a spring application which reads data from the Database and sends it to system 'X'. I am using task executors to spin up threads, so there are like 5 threads which are reading the database for rows at the same time. For each thread I need to make sure that unique records are selected.
To achieve this I am using JdbcTemplate and "select for update"
I have written the code but in the logs I am able to see 2 threads picking up the same rows. I am not able to figure out the root cause of this issue.
Does anyone has a suggestion
try {
List<Map<String, Object>> rows = getJdbcTemplate().queryForList(
SELECT_FOR_UPDATE,
new Object[] {a,b,c,d});
for (Map<String,Object> row : rows) {
Header a = new Header();
a.setMailID(((BigDecimal)row.get("mailID")).intValue());
a.setVersion(((BigDecimal)row.get("version")).intValue());
// some other parameters to get
getJdbcTemplate().update(UPDATE_MSG_STATE_VERSION_N_ORIG_MSG_STAT,
x,
a.getVersion()+1,
y),
a.getMailID(),
a.getVersion());
headers.add(a);
}
}
UPDATE_MSG_STATE_VERSION_N_ORIG_MSG_STAT = update MESSAGE set MSG_STAT_CD = ?, VERSION_NBR = ?, ORIG_MSG_STAT_CD=?, LAST_UPD_TS=SYSTIMESTAMP where MESSAGE.MAIL_ID = ? and VERSION_NBR = ?
String SELECT_FOR_UPDATE = "select m.MAIL_ID mailID, m.VERSION_NBR version, m.MSG_STAT_CD state,"
+ "from message m "
+ "and m.MSG_STAT_CD in ('Nerwerw')"
+ " and m.create_ts > (sysdate - ?)"
+ " and mod(mail_id,?) = ?"
+ " and ROWNUM <= ?"
+ " order by mt.MSG_PRIORITY FOR UPDATE";
You need to annotate your class with #Repostitory tag and the #Transactional tag to make sure that all the actions in the same call are handled in one transaction.
If they are not handled in the same transaction then each SELECT_FOR_UPDATE will happen on a different transaction and thus your threads queries will not be syncronized and your select_for_update does not matter.
Have you had transaction control properly set up?
If not, the transaction will only happen for the duration of the update statement, and will be committed automatically (You are using Oracle I believe, base on your syntax).
That means, although you acquired the lock of those records, they are released right-away.
Do you have access to modify the database? If I understand your question correctly I recently had a similar problem and implemented a scheme like this:
Add a new column to your database like "thread_number" or something like that. Set it to some default value like 0. Give each thread a unique identifier. Then you "claim" a record in the database by updating its "thread_number" to the identifier of the thread processing it. Then the other threads will not find it when querying if you include "where thread_number = 0" in the SQL.
I know it's kind of broad, but I hope it helps.

SQL Deleting from multiple tables in different databases without common elements

What I have are 7 tables, one of which is a master list and the other 6 are archives. Also, the archives are on another database. I need to go through the archive tables and delete rows, by checking 3 different column IDs (top, middle, bottom) that are not listed in the master table, as they are not relevant anymore. My SQL statement is below. I don't have a way to test it yet, but since I am not very familiar with SQL I was hoping people could give a few tips.
String[] tables;
tables = new String[]{"archive1", "archive2", "archive3",
"archive4", "archive5", "archive6"};
String query;
Statement stmt;
String objs = "TOP AND MIDDLE AND BOTTOM";
while(i<tables.length){
//TODO: CONFIRM THE QUERY IS CORRECT
query = "DELETE FROM "+ tables[i] + " WHERE "+ objs +
"NOT IN(SELECT " + objs + " FROM DB.masterTable WHERE " +
objs + " IS NOT NULL)";
//IS NOT NULL may not be necessary
try{
//TODO: VERIFY CONNECTION IS CORRECT
stmt = this.DB2.createStatement();
stmt.executeUpdate(query);
}catch(SQLException x){
System.out.println("Failure in loop queries!");
}
i++;
}
}
In the tables there are many columns, but I am concerned with comparing the top, middle, and bottom IDs (together they are unique to each row, but e.g. top might have many of the same values). So like I said, if there isn't a row with the same TOP, MIDDLE, BOTTOM in the master table, OBJECTS, then that row can be deleted from the archive that has it listed. I tried to put everything in one query but maybe I need multiple?
My main questions are:
A) Is my query correct in any sense?
B) Since the tables are on 2 different databases how should I handle that?
Solution found:
query = "DELETE FROM "+ tables[i] +
" WHERE (TOP, MIDDLE, BOTTOM) NOT IN "+
"(SELECT TOP, MIDDLE, BOTTOM FROM DB1.DB.masterTable)";
Main problem became figuring out how to compare all 3 fields of each row at a time and accessing the DB tables
A) No. There are some fundamental errors in your query. If you expand out your query, where objs = "OBJ_ID_TOP AND OBJ_ID_MIDDLE AND OBJ_ID_BOTTOM"
query = "DELETE FROM "+ tables[i]
+ " WHERE OBJ_ID_TOP AND OBJ_ID_MIDDLE AND OBJ_ID_BOTTOM
NOT IN(SELECT OBJ_ID_TOP AND OBJ_ID_MIDDLE AND OBJ_ID_BOTTOM
FROM db1Connection.OBJECTS
WHERE OBJ_ID_TOP AND OBJ_ID_MIDDLE AND OBJ_ID_BOTTOM IS NOT NULL)";
This is not proper SQL... If we take a glance at the wiki page on the WHERE clause,
The proper syntax for writing SQL Where clause is
SELECT <> FROM table WHERE column operatorvalue
Use AND and OR to string up multiple column conditions in your WHERE clause.
Also, AND is not valid in a SELECT statement. If you want to combine multiple result sets, use UNION.
Something of the form (see below) is closer to what you need. (Note: it is not optimized by any means... just a demonstration)
EDIT Think I originally misunderstood what you were trying to do... but I think you want to delete from some_table, not master_table.
--DELETE --can swap out SELECT for DELETE when the selected results look right
SELECT s.*
FROM some_table s --this is table[i]
LEFT OUTER JOIN master_table mt --db1Connection.OBJECTS
on s.ID_TOP = mt.ID_TOP
AND s.ID_MIDDLE = mt.ID_MIDDLE
AND s.ID_BOTTOM = mt.ID_BOTTOM
WHERE mt.ID_TOP IS NULL
AND mt.ID_MIDDLE IS NULL
AND mt.ID_BOTTOM IS NULL
B) I can't help you with this question... someone with more DB2(?) chops can help you
You need to get the list of data to be deleted from the first DB.
Then go for deletion in the second DB
And, you just can't compare saying obj1 and obj2 and obj3 not in (some list), it should be like obj1 not in (somelist) and obj2 not in (somelist) and obj3 not is(whatever).
In this case, it would be a good idea to create a temp table in DB2 where the archive tables are, with data from the master table in DB1. Then, run queries like
delete from archive1 where col1 not in (select col_master from temp_table);

getresultlist - query ok but data duplicate in List

I have a problem with getResultList().
My query is OK when it's executed and return 700 results.
In the return List, I had 700 results but the list contains duplicate data.
So I do not have all results.
public List<EscaleCatalogueKaravel> obtenirListeEscaleKaravelSelonMarche(Integer refMarche, Integer refLangue) {
List<EscaleCatalogueKaravel> listeEscales = entityManager.createQuery("select distinct p from EscaleCatalogueKaravel p " +
"where p.refMarche=:refMarche and p.refLangue=:refLangue group by idEscale ")
.setParameter("refMarche", refMarche)
.setParameter("refLangue", refLangue)
.getResultList();
if (listeEscales == null || listeEscales.size() == 0) {
return null;
}
return listeEscales;
}
Have you got an idea ?
You're using MySQL, right? Oracle would not execute the query but throw an error instead.
For correct usage of the group by clause you're only allowed to select that rows (or expressions) which are also mentioned in the group by clause. If you select a row which is not in the group by clause, this row might have different values for the members of one group. Which of these values the database should return? MySQL arbitrarily returns one of the possible values, but that is not correct.
In your query you either only do select distinct idEscale from ... or you group by all necessary columns and only select that ones or you drop your group by clause. By the way, distinct also can be used without group by, and distinct only should be used if really necessary because it makes the query slow.

Table will not UPDATE sometimes through a Hibernate native query

I have an entity, let's call it X. (the entity has a #Id id).
The entity is mapped to a table, let's call it: X_TABLE.
It also contains a
#ManyToOne
#JoinColumn(name = "JOIN_COLUMN")
YClass joinColumn;//another entity
I'm working with the following code:
X x = new X(xDTO);//just a copy constructor, nothing fancy, without id
x.joinColumn = null;
entityManager.persit(x);
entityManager.flush();
join_column_id = somne_function();//irelevant
Query q = entityManager.createNativeQuery(
"UPDATE X_TABLE t SET t.JOIN_COLUMN=" + join_column_id + " WHERE " + " t.id= " + x.getId());
q.executeUpdate();
q.flush();
The point in doing this, in case anyone is wondering, is that join_column is a foreign key to another table, in a different database, and I'm setting it through a native query because the coresponding table does not exist in the same database containing X_TABLE. But this isn't the problem. The enitity mappings and relations are all fine, I can garantee for that.
The code above is executed for several records in a loop, and it sometimes works fine (saves and updates the table fine) for all records. But sometimes it works fine for all records except for the last one in the loop (in the sense that it saves the record, but does not execute the update in the database). There are no Exceptions thrown and the JBoss server debug log for hibernate shows the UPDATE happening with the right values, with no error. Yet, the table contains null in JOIN_COLUMN for just one record and I have no ideea why. Any thoughts?
PS: The service is a Container Managed Transaction.
Few suggestions:
Check the number of rows affected by executeUpdate()
What is your Transaction policy? Any TransactionManager defined?

Categories

Resources