How can I do a bulk delete with Spring JPA repository? - spring

I just wrote a #Scheduled method which deletes old rows from a table.
#Component
class MyScheduledTasks {
#Scheduled(...)
public void purge_MyEntity() {
LocalDateTime max = ...;
repository.deleteAllByCreatedDateBefore(max);
}
}
And the log shows that the repository
first selects all entities by condition
and then remove each of them one by one
SELECT ... FROM ... WHERE
DELETE FROM ... WHERE ...
DELETE FROM ... WHERE ...
DELETE FROM ... WHERE ...
DELETE FROM ... WHERE ...
DELETE FROm ... WHERE ...
How can I make the repository execute a single DELETE FROM statement?

Related

Repository.saveAll throws InvalidDataAccessApiUsageException

I query a Bugzilla REST API with Spring, which returns a JSON object with the following structure:
https://bugzilla.mozilla.org/rest/bug/35
I have the following JPA class (excerpt):
#Entity
#Table
public class bug {
....
#ManyToOne (fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn (name = "assigned_to_user_id")
#SerializedName ("assigned_to_detail")
protected BugUser assignedTo;
#ManyToMany (cascade = CascadeType.ALL)
#JoinColumn (/ * ... shortened ... * /)
#SerializedName ("c_detail")
protected List <BugUser> cc;
...
}
The web service call takes place with the help of the Spring remainder template. Then the object is mapped from the rest template into my JPA class. However, when the data set is persisted by the Spring repository, I get the following exception:
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException:
Multiple representations of the same entity [BugUser # 2] are being
merged. Detached: [BugUser # 21]; Detached: [BugUser # 12];
As soon as I only comment one member variable of type BugUser in the Bug class, everything works.
How can I work around this problem?
You will have to make sure there is only one object with the same primary key. You can go through the list before merging and do something like the following:
Bug b = ...
BugUser assignee = b.assignedTo;
ListIterator<BugUser> iter = b.cc.listIterator();
while (iter.hasNext()) {
if (assignee.id.equals(iter.next().id) {
iter.set(assignee);
}
}
This way you ensure that the objects in the list of the same primary key are the same as the assignee. That is exactly why Hibernate complains.

Looping over items of a jpa streamresult and call an update service. Envers create an revision over all items instead for every single item

I loop over person entities of a jpa streamresult and call for every single person an update service bean to update the name of the person entity. I understand, that envers is executed at the end of the transaction. Ok this works fine and all entities are updated and have an AUD table entry, but with a single revision for all.
How I tell spring to do a for every person entity a single transaction , so that envers writes for every updated entity a single update revision instead for all updated person entities? I tried also to put #Transactional(propagation = Propagation.REQUIRES_NEW) to the top of the update service class, but envers isn't triggered for every item. It seems that all updates are executed in one transaction, but I need a single transacion for every call of the update service bean.
The stream service:
#Service
class StreamService {
#Autowired
PersonRepository repo;
#Autowired
FooService fooService;
#Transactional
public void uppercaseAllNames() {
Stream<Person> stream = repo.findAllPersons();
// change name for each person
stream.forEach(fooService::doFoo);
}
}
The simplified update service:
#Service
#Transactional(propagation = Propagation.REQUIRES_NEW) // <=== create new transaction
class FooService {
#Autowired
PersonRepository repo;
doFoo(Person person) {
String name = person.getName();
person.setName(name.toUpperCase());
repo.save(person); // <=== save trigger envers
}
}
Solution:
The save operation trigger envers to create a revision per person entity, but this solution works in our project only with #Transactional(propagation = Propagation.REQUIRES_NEW). A single #Transactional doesn't work. The annotation can be placed at method or class level, booth places work.
Remove the #Transactional from uppercaseAllNames.
This will give you a separate transactions for the read and each write.
You'll need to add a personRepo.save(person) to the FooService.doFoo in order to persist the changes.
It might be that the second change is sufficient with Propagation.REQUIRES_NEW, but I find nested transactions rather confusing and would recommend to avoid them.
Had the same Problem and the Accepted Answer above worked. But I had to add readOnly=true to the outer Transaction.

Spring Data JPA + Oracle Trigger increments the ID twice

I use the following tech stack:
spring-boot-starter-data-jpa
HikariCP for connection pooling
Oracle DB
My actual code looks similar to this.
/// My trigger looks like this
CREATE OR REPLACE TRIGGER FILE_BRI
BEFORE INSERT
ON FILE
FOR EACH ROW
BEGIN
SELECT FILE_SEQ.NEXTVAL INTO :NEW.ID FROM DUAL;
END;
///
public class FILE implements Serializable {
#Id
#SequenceGenerator(
name = "FILE_SEQ",
sequenceName = "FILE_SEQ",
allocationSize = 1)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "FILE_SEQ"
)
private long id;
}
public class ServiceA () {
#Transactional(propagation = REQUIRES_NEW, isolation = READ_COMMITTED)
public File insertFile() {
// Below line returns the inserted File object with ID as '58496'
return fileRepository.save(file)
}
#Transactional(propagation = REQUIRES_NEW, isolation = READ_COMMITTED)
public AccessControl insertAccessControl() {
// Below line results in 'SQLIntegrityConstraintViolationException' (full error at the bottom of this post)
return accessControlRepository.save(accessControlFile)
}
}
Public class FileProcessor() {
ServiceA serviceA;
public void someMethod() {
// insert the file and get the inserted record
File insertedFile = serviceA.insertFile(file);
// get the ID from the inserted file and make another insert into another table
serviceA.insertAccessControl(insertedFile.getId()); // inserted file ID is '58496'
}
}
This is my investigation:
When I verified the ID of the inserted record in the table "FILE" is '58497', however repository.save() returned a different value.
When I make the second insert on table "ACCESS_CONTROL_FILE" with FILE_ID as '58496' it results in the error below because the FILE with ID as '58496' does not exist.
Caused by: java.sql.SQLIntegrityConstraintViolationException: ORA-01400: cannot insert NULL into ("DB_OWNER"."ACCESS_CONTROL_FILE"."FILE_ID")
I'm puzzled as to why would repository.save() return a different ID(i.e. ID=58496) than what is actually inserted(ID=58497) in the database!
I've investigated all options that I could find on the internet related to 'Propagation and Isolation'.
As mentioned in comments, Looks like a database trigger is causing the issue. Disable the trigger to let JPA to manage the ID generation.

In Spring data JPA ,How to query data from a table without a repository for entity

Is it possible to fetch data from a table without creating a JPA repository for this specific table.
I need to do this as there are considerable number of entities which I have to do a simple query , it would be a waste to create repositories for each of them.
You can simply inject an EntityManager to any component:
#Component
class SomeComponent {
#PersistenceContext
private EntityManager entityManager;
public List<SomeEntity> findAllEntities() {
TypedQuery<SomeEntity> query = entityManager.createQuery("SELECT e FROM SomeEntity e", SomeEntity.class);
return query.getResultList();
}
}
Also, if your entities have the same superclass, you can use the same Repository for all of them, like described there.

Spring Data MongoDB repository method delete by list of id

I have the following document:
#Document(collection = "decision_analysis")
public class DecisionAnalysis implements Serializable {
#Id
private String id;
...
}
I need to delete multiple MongoDB documents via List<String> decisionAnalysisIds
How to properly write Spring Data MongoDB repository method in order to do it?
The following doesn't work :
void deleteByIds(List<String> decisionAnalysisIds); - error: No property ids found for type DecisionAnalysis! Did you mean 'id'?
void deleteById(List<String> decisionAnalysisIds); - works, but delete only one document
Use the in clause like this:
void deleteByIdIn(List<String> ids);

Resources