Spring data JPA native query skip locked - spring

I want to execute a SKIP LOCKED query on Oracle using Spring Data JPA, so I tried the following:
#Lock(LockModeType.PESSIMISTIC_WRITE)
#Query(value = "SELECT * FROM User WHERE ID=?1 FOR UPDATE SKIP LOCKED", nativeQuery = true)
User findOne(UUID id);
I tried the above and found that the generated query contains FOR UPDATE, but not SKIP LOCKED (below is the generated query from logs):
select ent0_.column1 as name, ent0_.CREATED_DATE as CREATED_2_33_0_ from TABLE_NAME alias_name where ent0_.column1=? for update
If I remove #Lock from the query method, the generated query does not even have FOR UPDATE.
Please suggest how I can generate a query with FOR UPDATE SKIP LOCKED, as required.

You can assing -2 to timeout value so that 'skip locked' will be used whenever possible.
PESSIMISTIC_WRITE with a javax.persistence.lock.timeout setting of -2
UPGRADE_SKIPLOCKED
The lock acquisition request skips the already locked rows. It uses a
SELECT …​ FOR UPDATE SKIP LOCKED in Oracle and PostgreSQL 9.5, or
SELECT …​ with (rowlock, updlock, readpast) in SQL Server.
public interface MyRepository extends CrudRepository<MyEntity, Long> {
/**
* The lock acquisition request skips the already locked rows.
* It uses a SELECT …​ FOR UPDATE SKIP LOCKED in Oracle and PostgreSQL 9.5,
* or SELECT …​ with (rowlock, updlock, readpast) in SQL Server.
*/
String UPGRADE_SKIPLOCKED = "-2";
#Lock(value = LockModeType.PESSIMISTIC_WRITE) // adds 'FOR UPDATE' statement
#QueryHints({#QueryHint(name = "javax.persistence.lock.timeout", value = UPGRADE_SKIPLOCKED)})
MyEntity findFirstByStatus(String status);
}
By doing like this, the select query will have select ... for update skip locked
https://docs.jboss.org/hibernate/orm/5.0/userguide/html_single/chapters/locking/Locking.html

Add:
#QueryHints({#QueryHint(name = "javax.persistence.lock.timeout", value
="-2")})

Related

Spring Data JPA - How do i get a list of specific rows by ids?

I'm trying to get specific rows from the table by ids, but what I have below is not working.
#Query(value = "SELECT * FROM row r where r.row_id = :row_ids", nativeQuery = true)
List<Object> temp(#Param("albumsIds") String row_ids);
row_ids is all the ids separated by an "or" - id:1 or id:2 or id:3
I'm just trying to do select * from row r where r.row_id = id:1, or r.row_id = id:2, or r.row_id = id:3
Does anyone have an idea what the problem is, or is there a better way to do it?
Simply use:
List<Object> findByIdIn(Collection<Integer> ids);
That will automatically setup the derived query from the query method name itself.
Alternatively, if you want to provide the query programmatically, then the query should be:
#Query(value = "SELECT * FROM row R WHERE R.row_id IN :ids", nativeQuery = true)
List<Object> temp(#Param("ids") Collection<Integer> ids);
MySQL lets use use the IN keyword in queries, which let's us provide a CSV for the IDs to parse and returns any records which ID is in the CSV. This obviously can be used on any data, just for this purpose we'll use the IDs as an example.
Using the method above should minimize the risk of SQL injection significantly, as the Java Collection type casting shouldn't allow a user to provide any values that can cause issues.

Not able to execute ALTER SEQUENCE command through spring boot code

I am writing a code in spring boot 1.5.22 with java 8 and oracle 11g. Here, In my repository class, I have tried to call one native query as-
#Query(value = "ALTER SEQUENCE <SEQ_NAME> RESTART START WITH 0", nativeQuery = true)
void resetSequence();
when I try to call this method in my service Impl class, I get the following error:-
java.lang.NegativeArraySizeException:-1
However, I can execute select sequence commands using java code as-
#Query(value = "select <Seq_name>.nextVal from dual", nativeQuery = true)
int getNextCount();
I don't know how exactly I can command to reset my sequence using java code/job here.
Try to add #Modifying annotation
Well, ALTER SEQUENCEis not a query, so it is not expected to work in the way you call it.
What will work is to fall back to plain JDBC and call something like this
con.createStatement().execute "ALTER SEQUENCE SEQ RESTART START WITH 0"
Two additionally remarks
the ALTER SEQUENCEis not a typical use case for spring-data so it should be used only in some supporting code for JUnit etc.
You'll have to create the sequence with MINVALUE 0 to be able to reset it (default is 1). Otherwise you get exception ORA-04006: START WITH cannot be less than MINVALUE
You can use the Entity manager to modify a sequence and execute SQL queries that the spring boot shortcuts do not allow
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
#PersistenceContext
EntityManager entityManager;
public void resetSequence(String data) {
entityManager
.createNativeQuery("SELECT setval('sequence_value', "+data+", FALSE);")
.getSingleResult()
;
}
you can use entity manager to reset your sequence
ORACLE
entityManager.createNativeQuery("DROP SEQUENCE " + seqName).executeUpdate();
entityManager.createNativeQuery("CREATE SEQUENCE " + seqName + " START WITH " + size).executeUpdate();
POSTGRESQL
entityManager.createNativeQuery("SELECT setval(?, ?, true)")
.setParameter(1, seqName)
.setParameter(2, size)
.getResultList();

Spring Data JPA - Select row for update

I have a requirement to read the first enabled account from DB2 database table and immediately update the column to disable it. While server 1 is reading and updating the column, no other server should be able to read the same row since I want one account to be used by only one server at a time.
This is what I have so far..
Account.java
Account{
private Long id;
private Character enabled;
.............
}
AccountRepository.java
public interface AccountRepository extends JpaRepository<Account, Long>{
Account findFirstByEnabled(new Character('Y'));
}
AccountServiceImpl.java
#Service
public class AccountServiceImpl {
#Autowrired
private AccountRepository accntRepository;
#Transactional
public Account findFirstAvaialbleAccount(){
Account account = accntRepository.findFirstByEnabled(new Character('Y'));
if(account != null)
{
account.setEnabled(new Character('N')); //put debug point here
account.save(proxyAccount);
}
return account;
}
}
But this isn't working.. I've put a debug pointer in the findFirstAvaialbleAccount() method. What I was expecting is, if the debug pointer reaches that line and waiting for me to resume execution, if I run select query directly on the database, the sql shouldn't execute. It should only execute after I resume the execution on the server so that transaction is completed. But instead, running the select query directly on the database gave me the complete result set immediately. What am I missing here? I'm using DB2 if it matters.
Answering my own question... I had incorrect Select SQL running against the database. If I run the select sql with "select.. for update", then the execution waits until I hit resume on the server and transaction is complete.
SQL 1 - this executes immediately even though the transaction from server isn't complete.
select * from MYTABLE where ENABLED = 'Y';
SQL 2- this waits until the transaction from server is complete (it will probably timeout if I don't hit resume quick enough)
select * from MYTABLE where ENABLED = 'Y'
fetch first 1 rows only with rs use and keep update locks;

SimpleJpaRepository Count Query

I've modified an existing RESTful/JDBC application i have to work with new features in Spring 4... specifically the JpaRepository. It will:
1) Retrieve a list of transactions for a specified date. This works fine
2) Retrieve a count of transactions by type for a specified date. This is not working as expected.
The queries are setup similarly, but the actual return types are very different.
I have POJOs for each query
My transactions JPA respository looks like:
public interface MyTransactionsRepository extends JpaRepository<MyTransactions, Long>
//My query works like a charm.
#Query( value = "SELECT * from ACTIVITI_TMP.BATCH_TABLE WHERE TO_CHAR(last_action, 'YYYY-MM-DD') = ?1", nativeQuery = true )
List< MyTransactions > findAllBy_ToChar_LastAction( String lastActionDateString );
This returns a list of MyTransactions objects as expected. Debugging, i see the returned object as ArrayList. Looking inside the elementData, I see that each object is, as expected, a MyTransactions object.
My second repository/query is where i'm having troubles.
public interface MyCountsRepository extends JpaRepository<MyCounts, Long>
#Query( value = "SELECT send_method, COUNT(*) AS counter FROM ACTIVITI_TMP.BATCH_TABLE WHERE TO_CHAR(last_action, 'YYYY-MM-DD') = ?1 GROUP BY send_method ORDER BY send_method", nativeQuery = true )
List<MyCounts> countBy_ToChar_LastAction( String lastActionDateString );
This DOES NOT return List as expected.
The object that holds the returned data was originally defined as List, but when I inspect this object in Eclipse, I see instead that it is holding an ArrayList. Drilling down to the elementData, each object is actually an Object[2]... NOT a MyCounts object.
I've modified the MyCountsRepository query as follows
ArrayList<Object[]> countBy_ToChar_LastAction( String lastActionDateString );
Then, inside my controller class, I create a MyCounts object for each element in List and then return List
This works, but... I don't understand why i have to go thru all this?
I can query a view as easily as a table.
Why doesn't JPA/Hibernate treat this as a simple 2 column table? send_method varchar(x) and count (int or long)
I know there are issues or nuances for how JPA treats queries with counts in them, but i've not seen anything like this referenced.
Many thanks for any help you can provide in clarifying this issue.
Anthony
That is the expected behaviour when you're doing a "group by". It will not map to a specific entity. Only way this might work is if you had a view in your database that summarized the data by send_method and you could map an entity to it.

execute preparedStatement in getSqlMapClientTemplate

I want to execute prepardStatement in getsqlMapClientTemplate() method.One of preparedStatement parameter value is received by another query result.Is it possible to execute like this?
like
String query="select Id_no from employee";
String resultQuery="select empSalary from employeePay where Id=?";
prepareStatement ps=con.preprepareStatement(query); // Instead of connection reference i want to use getSqlMapClientTemplate
ResultSet rs=ps.executeQuery();
while(rs.next()){
//pst.setInt(1,rs.getInt(1)); // here if i want to pass dynamic value to execute resultQuery
pst.setInt(1,userGivenValue);
PrepareStatement pst=con.prepareStatement(resultQuery); //
pst.executeQuery();
forpreparedStatement instead of connection object i want to use getSqlMapClientTemplate() method.
You don't want a SqlMapClientTemplate you want a JdbcTemplate instead.
Next your solution is flawed as it falls into the 1+N select problem, ie. you execute a single query to get a list of ids then for each id issue another query. Creating a single query which does everything in one shot is more effecient.
final String query = "select empSalary from employeePay where Id in (select Id_no from employee)"
JdbcTemplate template = getJdbcTemplate();
List<Long> salaries = template.queryForList(query, Long.class);
Something like that.
Or if you want to use the SqlMapClientTemplate simply add the query to the ibatis configuration and let iBatis (instead of the JdbcTemplate) handle the hard lifting for you.

Resources