Custom update in CrudRepository - spring

is that possible to returns updated entity by custom update method instead of numbers of affected rows? How can I achieve this?
I would like to have sth like this:
public interface DataRepository extends CrudRepository<Data, Long> {
#Modifying
#Query(value="UPDATE data SET max_version = max_version + 1 WHERE id = 'A'", nativeQuery=true)
Data updateDataByType();
}
instead of this
public interface DataRepository extends CrudRepository<Data, Long> {
#Modifying
#Query(value="UPDATE data SET max_version = max_version + 1 WHERE id = 'A'", nativeQuery=true)
Integer updateDataByType();
}

You cannot do this with the #Modifying annotation. because these methods can only be void and int. Otherwise you will get the error Modifying queries can only use void or int / Integer as return type.
But can be you can implement custom repository Implementation and return your updated entity after done with the query execution.
Reference: Custom Implementations for Spring Data Repositories

Related

Get fields of spring jpa interface projection

I have this stored procedure that I am calling in spring jpa repository and I am using interface based projection.
Whenever I try to call the interface projection method i get this error
Invoked method public abstract java.lang.Long
ConfirmationDTO.memberID() is no accessor method!
Here is my projection Interface
public interface ConfirmationDTO {
Long memberID();
LocalDate dateEntry();
}
and the DAO
#Query(value=" CALL get_confirmation(:startDate) ", nativeQuery=true)
List<ConfirmationDTO> getConfirmation(LocalDate startDate);
Is it possible to get the field values from the interface projection ?
I found another SO thread which uses Tuple, this helped me to achieve the goal of my above question.
how-to-map-sql-native-query-result-into-dto-in-spring-jpa-repository
Here is the sample code from that thread :
#Repository
public interface StockRepository extends RevisionRepository<Stock, Long, Integer>, JpaRepository<Stock, Long> {
#Query(value = "SELECT stock_akhir.product_id AS productId, stock_akhir.product_code AS productCode, SUM(stock_akhir.qty) as stockAkhir "
+ "FROM book_stock stock_akhir "
+ "where warehouse_code = (:warehouseCode) "
+ "AND product_code IN (:productCodes) "
+ "GROUP BY product_id, product_code, warehouse_id, warehouse_code", nativeQuery = true)
List findStockAkhirPerProductIn(#Param("warehouseCode") String warehouseCode, #Param("productCodes") Set productCode); }
and them map the Tuple in the service:
public List<StockTotalResponseDto> findStocktotal() {
List<Tuple> stockTotalTuples = stockRepository.findStocktotal();
List<StockTotalResponseDto> stockTotalDto = stockTotalTuples.stream()
.map(t -> new StockTotalResponseDto(
t.get(0, String.class),
t.get(1, String.class),
t.get(2, BigInteger.class)
))
.collect(Collectors.toList());
return stockTotalDto;
}
Let me try to explain how you can easily do this.
public class Confirmation {
private Long memberId;
private LocalDate dateEntry;
//add other fields
//provide getters and setters
}
//tuple
public inteface ConfirmationTuple {
Long getMemberId ();
LocalDate getDateEntry ();
}
//Your repository
#Query(value = " CALL get_confirmation(:startDate) ", nativeQuery = true)
List<ConfirmationTuple> getConfirmation (LocalDate startDate);
Spring will do the rest for you. To get the memberId from the first tuple, all you do is
yourDAO.getConfirmation(startDate).get(0).getMemberId();
The catch here is the get methods in your tuple must correspond to the field names being returned by your query in your repository. For example, if your query is returning the following columns [memberName,myDate] your Tuple interface must have getMemberName and getMyDate() for these values to be assigned.
You could create an implementation component and it would be autowired, but it's not recommended to annotate DTO classes.
The easiest way is to turn your interface into a class.
At the end of the day it's just a DTO, it have no logic, and on tests you can mock it as you wish just filling the properties.
I don't see the point on your DTO being an interface, unless a Class somewhere is implementing more than one interface and this one is among them.
If this is the case, I would rethink the implementation - e.g. implement TheOtherInterface extend Person.

spring data jpa saveandflush method

I do understand the difference between the method save and the method saveAndFlush of the class JpaRepository Spring Data JPA. As per my understanding, the save method will run and commit the sql only at the end of the transaction whereas the saveAndFlush method will synchronize the persistence context with the database by running the SQL statement but without committing it. Below is a sample code where I Wanted to experience with it and please review it.
This is the repository class for the update
#Repository
public interface ClassRepository extends JpaRepository<ClassA, Long> {
#Modifying(clearAutomatically = true)
#Query(value = "UPDATE class e SET e.class_name = ? WHERE e.employee_id = ?", nativeQuery = true)
int updateClassNative(String className, String empId);
}
This is the test case where I am testing the methods
#Test
void saveAndUpdateWithFlushJPA() {
ClassA classA = ClassA.builder().className("Test").employeeId("S0810").build();
this.classRepository.save(classA);
int size = this.classRepository.updateClassNative("TestQ", "S0810");
assertThat(size).isEqualTo(1);
}
In the above test case, the test passed. I was not expecting the record to be saved since I am using the save method. In the source code, the save method is wrapped with #Transactional. Is it because of that the save method is already committing the insert statement?
Ashley
The problem with your test scenario is that JPA always flushes the persistence context before executing a native query (this is also the default behaviour for JPQL queries, though it can be overriden). The rationale is that a query should report a state reflecting the changes already made in the current unit of work.
To see the difference between save/saveAndFlush, you can use the following test cases instead:
#Repository
public interface ClassRepository extends JpaRepository<ClassA, Long> {
#Query("SELECT COUNT(c.id) FROM ClassA c")
#QueryHints({
#QueryHint(name = org.hibernate.annotations.QueryHints.FLUSH_MODE, value = "COMMIT")
})
int countClassAEntities();
}
#Test
#Transactional
void saveAndUpdate() {
int initialCount = classRepository.countClassAEntities();
ClassA classA = ClassA.builder().className("Test").employeeId("S0810").build();
classRepository.save(classA);
int finalCount = classRepository.countClassAEntities();
assertEquals(initialCount, finalCount);
}
#Test
#Transactional
void saveAndUpdateWithFlush() {
int initialCount = classRepository.countClassAEntities();
ClassA classA = ClassA.builder().className("Test").employeeId("S0810").build();
classRepository.saveAndFlush(classA);
int finalCount = classRepository.countClassAEntities();
assertEquals(initialCount + 1, finalCount);
}
In the above setup, the count query has flush mode set to COMMIT, meaning that executing the query will not trigger a flush. If you were to use the default repository.count() method instead, the first test case would fail because by default, the flush mode is set to AUTO.

Spring Boot update field in database hardcoded

i have this in my repository
#Transactional
#Modifying
#Query(value = "UPDATE pengajuan u set u.status_approval ='Komite' where u.id_pengajuan =:idPengajuan",
nativeQuery = true)
void updateStatusPengajuan(#Param("statusApproval") String statusApproval, #Param("idPengajuan") Integer idPengajuan);
i want to set status_approval to 'Komite' by 'idPengajuan'
now i have this in my services
public PengajuanK3 update(String statusApproval,int idPengajuan){
return pengajuanK3Repository.updateStatusPengajuan(statusApproval, idPengajuan);
}
im littlebit confuse how i can call the repository in services because in repository is void type.
The query that you have used always sets status_approval to Komite. In this sense, you don't need to pass the parameter in your repository update method. Everything is fine.
But if you want to update status_approval dynamically from the parameter other than 'Komite' then do like this:
Repository:
#Transactional
#Modifying
#Query(value = "UPDATE pengajuan u set u.status_approval =:statusApproval where u.id_pengajuan =:idPengajuan",
nativeQuery = true)
void updateStatusPengajuan(#Param("statusApproval") String statusApproval,
#Param("idPengajuan") Integer idPengajuan);
And in your service change the return type to void and remove return.
public void update(String statusApproval,int idPengajuan){
pengajuanK3Repository.updateStatusPengajuan(statusApproval,idPengajuan);
}
In your controller, call the update method like:
service.update('Komite', 1);
I prefer this way rather than hardcoding because in the future if you need to set status_approval to other values, you can do it by:
service.update('othervalues', 1);

Spring Boot audit #Query

It's possible to user Spring boot Audit with JPA #Query annotation?
For example i have the next Repository:
#Repository
public interface ItemRepository extends JpaRepository<Item, Long> {
#Modifying
#Query(value = "update Item i set statusId = :statusId WHERE i.id = :id")
void updateById(#Param("id") Long id, #Param("statusId") Long statusId);
}
But when i use this into my service it's not updating update_date column in database:
#Autowired
private ItemRepository itemRepository;
#Transactional
public void updateItemStatus(Long itemId, Long statusId) {
//Case 1: This is working, update_date column is updated
Item item = itemRepository.findById(itemId).orElseThrow(() -> new ResourceNotFoundException("Item", "id", itemId));
item.setStatusId(statusId);
itemRepository.save(item);
//Case 2: This is not working
itemRepository.updateById(itemId, statusId);
}
So, can i use Audit with #Query annotation ?
Thank you.
Auditing is based on the JPA Lifecycle events. Only the methods directly manipulating instances (persist, merge and remove) trigger such events.
The execution of queries, modifying or otherwise, does not trigger any events and therefore, won't cause auditing to happen.
You have syntax issue
Either use
#Query(value = "update Item i set i.statusId = :statusId WHERE i.id = :id")
or
#Query(value = "update Item set statusId = :statusId WHERE id = :id")

Spring DATA JPA how to write a method which use contant value for a field to fetch data

Hi I am using Spring Data JPA and want to use feature generate query from method name. I have a field active in DB which have only value 0 and 1. I want to fetch all data with which have active value is 1.
This is a constant value so i don't want to pass this value as method arguments.
please suggest what will be the method for the same.
example:
I have a entity EmailRef
public class EmailRef {
/* other vareialbe */
#Column(name="is_active") /* this is the field which value is 0 and 1 in DB*/
private Integer active;
/* setter getter method */
}
This is the repository for where I want to write method which will fetch all data for which active is 1;
public interface EmailRefRepositry extends JpaRepository<EmailRef, Long> {
#Query("select * from email_reference where is_active=1") /* this is the query I want to convert into method*/
List<EmailRef> findByActive(); /*I want to write method like that which will fetch all data form table where active field value is 1*/
}
I am stuck for constant vale please suggest
Thanks
Sudhanshu
If you could change that Integer to a boolean, you could be doing something like:
In your entity:
private Boolean active;
In your repo:
List<EmailRef> findByActiveIsTrue();
Try this:
public interface EmailRefRepositry extends JpaRepository<EmailRef, Long> {
#Query("select e from EmailRef e where e.active=1")
List<EmailRef> findOnlyActiveWithQuery();
default List<EmailRef> findOnlyActive() {
findByActive(1);
}
default List<EmailRef> findNotActive() {
findByActive(0);
}
List<EmailRef> findByActive(Integer active);
}
I don't think you can do what you want using Spring JPAs magic where is derives the query from the method name (unless you are able to do as #kimy82 suggests in their solution). You can of course use the #Query annotation on your repository method though. However the one you have defined won't work because it is a native query and you have no specified that. Here are two possible fixes to your Query annotation although I would recommend the first:
#Query("select e from EmailRef e where e.active=1")
or
#Query("select * from email_reference where is_active=1", nativeQuery=true)

Resources