#Transaction readOnly on Spring repo and save on another repo - java

I have implemented following code part:
public void computeAllBilling() {
orderRepository.getAll().parallel().forEach(ordreEntity -> {
Billing billing = computedBilling(orderEntity);
billingRepository.save(billing);
entityManager.detach(ordreEntity);
});
}
public interface BillingRepository extends JpaRepository<BillingEntity, String> {
#QueryHints(value = {
#QueryHint(name = org.hibernate.jpa.QueryHints.HINT_FETCH_SIZE, value = "1"),
#QueryHint(name = org.hibernate.jpa.QueryHints.HINT_CACHEABLE, value = "false"),
#QueryHint(name = org.hibernate.jpa.QueryHints.HINT_READONLY, value = "true")
})
#Query(value = "SELECT o.* FROM raw.ordre o", nativeQuery = true)
Stream<BillingEntity> getAll();
}
I need to stream my reading very quickly from one table, but in our business processing, we need to save the billing at the ending (after reader one order its billing must be saved)
But following this link: https://www.geekyhacker.com/2019/03/26/high-performance-data-fetching-using-spring-data-jpa-stream/
I need finally to save in another repository, not the reader repository.
Anyone know a mean to avoid
"org.springframework.dao.InvalidDataAccessApiUsageException: You're trying to execute a streaming query method without a surrounding transaction that keeps the connection open so that the Stream can actually be consumed. Make sure the code consuming the stream uses #Transactional or any other way of declaring a (read-only) transaction.
"
please ? Great thank, and best regards
Adrian

Related

Spring Data JPA flush does not save changes to database

I have the following code that first check record and if found delete that record and flush changes to the database. However, when I debug, I see that it does not reflect changes to the database when debugger hit the next code block (final Stock stock = new Stock();).
#Transactional
public CommandDTO createOrUpdate(StockRequest request) {
stockRepository.findByBrandUuidAndProductUuid(
request.getBrandUuid(),
request.getProductUuid())
.ifPresent(stock -> {
stockRepository.delete(stock);
stockRepository.flush();
});
final Stock stock = new Stock();
if (request.isOutOfStock()) {
stock.setBrandUuid(request.getBrandUuid());
stock.setProductUuid(request.getProductUuid());
stock.save(stock);
}
return CommandDTO.builder().uuid(stock.getUuid()).build();
}
So, what is the mistake in this approach?
JPA doesn't supports final field.
You can use two alternative solution for immutable class.
use #Immutable at entity class.
change entity class fields having only a getter.

Javers not recognizing insert as an initial change

Working on a SpringBoot application using MongoDB as a persistent store.
Using spring data and MongoRepository to access MongoDB.
Using Javers to provide auditting.
If I use mongoRepository.insert(document) followed later by a mongoRepository.save(document) and then use javers to query the changes to that document, javers does not detect the differences between the object inserted and the object saved. It reports only a single change as if the save call was the original object persisted.
If I replace the insert call with a save and let spring data handle whether or not to insert or update, javers reports the expected change.
Example:
Consider the following:
#JaversSpringDataAuditable
public interface SomeDocumentRepository extends MongoRepository<SomeDocument, String> {
}
#Builder
#Data
#Document(collection = "someDocuments")
public class SomeDocument {
#Id
private String id;
private String status;
}
#Service
public class SomeDocumentService {
#Autowired
private SomeDocumentRepository someDocumentRepository;
public SomeDocument insert(SomeDocument doc) {
return someDocumentRepository.insert(doc);
}
public SomeDocument save(SomeDocument doc) {
return someDocumentRepository.save(doc);
}
}
#Service
public class AuditService {
#Autowired
private Javers javers;
public List<Change> getStatusChangesById(String documentId) {
JqlQuery query = QueryBuilder
.byInstanceId(documentId, SomeDocument.class)
.withChangedProperty("status")
.build();
return javers.findChanges(query);
}
}
If I call my service as follows:
var doc = SomeDocument.builder().status("new").build();
doc = someDocumentService.insert(doc);
doc.setStatus("change1");
doc = someDocumentService.save(doc);
and then call the audit service to get the changes:
auditService.getStatusChangesById(doc.getId());
I get a single change with "left" set to a blank and "right" set to "change1".
If I call "save" instead of "insert" like:
var doc = SomeDocument.builder().status("new").build();
doc = someDocumentService.save(doc);
doc.setStatus("change1");
doc = someDocumentService.save(doc);
and then call the audit service to get the changes I get 2 changes, the first being the most recent change with "left" set to "new", and "right" set to "change1" and a second change with "left" set to "" and "right" set to "new".
Is this a bug?
That's a good point. In case of Mongo, Javers covers only the methods from the CrudRepository interface. See https://github.com/javers/javers/blob/master/javers-spring/src/main/java/org/javers/spring/auditable/aspect/springdata/JaversSpringDataAuditableRepositoryAspect.java
Looks like MongoRepository#insert() should be also covered by the aspect.
Feel free to contribute a PR to javers, I will merge it. If you want to discuss the design first - please create a discussion here https://github.com/javers/javers/discussions

How to add ROOM queries that take parameters and return data

I am trying to create a new room database query that takes a parameter and returns a list. All the documents I read and videos I watch only show me as far as the DAO query (which I have done) but what I cannot find is how to create the subsequent queries for repository and viewModel classes.
This is my DAO query;
#Query("SELECT * FROM member WHERE name = :reselectedPlayerName")
List<Member> getPlayersForReselection(String reselectedPlayerName);
I have successfully created a 'LiveData' query (for another task) which does not take any parameters, but I do NOT want a Livedata query this time and I cannot see how to create the query in the repository class.
The answer as always is generally simple!... but when you cannot see the wood for the trees....
If anybody wants more detail, I am happy to detail what I found and what I did but this is what I ended up doing;
(I know I said I didn't want to use LiveData but I did just for proving)
DAO
#Query("SELECT * FROM member WHERE name = :aStr")
LiveData<List<Member>> getPlayersForReselectionDb(String aStr);
REPOSITORY
public LiveData<List<Member>> getPlayersForReselectionDb(String aStr){
reselectedMembers = memberDAO.getPlayersForReselectionDb(aStr);
return reselectedMembers;
}
VIEWMODEL
public LiveData<List<Member>> getPlayersForReselectionDb(String aStr) {
reselectedMembers = memberRepository.getPlayersForReselectionDb(aStr);
return reselectedMembers;
}
FRAGMENT
MemberViewModel memberViewModel = new
ViewModelProvider(this).get(MemberViewModel.class);
memberViewModel.getPlayersForReselectionDb("Fred Bloggs").observe(this,
new Observer<List<Member>>() {
#Override
public void onChanged(List<Member> members) {`enter code here`}
}
});

How to perform soft delete using JPA #Query and #Param Annotations?

I am trying to update the existing rows in database table using JPA #Query Annotation. I want to perform Soft delete by updating the Deleted_Flag to YES from NO.
Here is my Code snippet:
#Modifying
#Query("UPDATE TBL_NAME SET DELETE_FLAG = 'YES' WHERE DELETE_FLAG = 'NO'
AND FILE_NM = :FILE_NM")
public void softDelete(#Param("FILE_NM") String fileName)
{
}
I am not getting any error, but data is not being updated in database.
Actual result must be like all the existing rows must be updated with DELETE_FLAG to YES.
Make sure you invoke the repository method with an active transaction.
Actually, in my last project I use the following idiom for updating a flag :
Entity is annotated with Hibernetish:
#Entity
#Table(name="myTable")
#Where(clause = "is_deleted = 0")
#Cacheable
public class MyTable {}
Actual update comes with a trivial find method:
#Transactional
public void deleteById(#NonNull final Long themeId) {
themeRepository.findById(themeId).orElseThrow(() -> new EntityNotFoundException(THEME_NOT_FOUND + themeId))
.setDeleted(true);
}

Spring Jpa Update: Can not issue data manipulation statements with executeQuery()

I try to use handle query for updating table in the SQL database.
Code:
#Autowired
private ProducerRepository producerRepository;
public void update(Producer producer){
String name = producer.getProducerName();
long id = producer.getId();
// producerRepository.save(producer); //this method works well.
producerRepository.update(name, id); //handle attempt - throws exeption in this string
}
ProducerRepository:
#Repository
public interface ProducerRepository extends JpaRepository<Producer, Long>{
#Query(nativeQuery = true, value = "UPDATE producer SET producer_name = :pName WHERE id = :id")
Producer update(
#Param("pName") String pName,
#Param("id") long id
);
}
All parameters of the producer entity are correct and producerRepository.save(producer) works well.
(also I out in console name and id fields - all right)
So, I can save producer in the database, but, when I try to use update() method I get the error.
Can not issue data manipulation statements with executeQuery()
PS
sql query in the console also works well
(UPDATE producer SET producer_name = 'some name' WHERE id = ....)
It should be noted that other SQL native queries in repository work correctly. So the spring/hibernate/jdbc settings are correct.
Use annotation #Modifying.
This will trigger the query annotated to the method as updating query
instead of a selecting one.
From 2.2.6 Modifying queries https://docs.spring.io/spring-data/jpa/docs/1.3.4.RELEASE/reference/html/jpa.repositories.html
In case above solution not work use this
#Modifying
#Transactional
#Query(value ="delete from admindata where user_name = :userName AND group_name = :groupName",nativeQuery = true)
public void deleteadminUser(#Param("userName") String userName,#Param("groupName") String groupName);
#Query(nativeQuery = true, value = "UPDATE producer SET producer_name = :pName WHERE id = :id")
Producer update(
#Param("pName") String pName,
#Param("id") long id
);
your update method is returning Producer Object, the return should be either int or void, since you are returning Producer entity, Spring JPA is thinking that it has to fetch the object instead of updating it, that is the reason it is executing executeQuery, instead of executeUpdate and also you need to #Modifying annotation.

Categories