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

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.

Related

How to get actual child collection when updating parent

How can I get actual child collection, when adding new one in separated transactional method, while updating parent.
I have spring boot app with hibernate/jpa and one-to-many unidirectional model:
parent:
#Entity
public class Deal {
private UUID id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Rate> rates;
....
}
child:
#Entity
public class Rate {
private UUID id;
....
}
And I have non transactional method for do some business logic by rest call:
public Deal applyDeal(UUID dealId) {
dealService.apply(dealId);
return dealService.getById(dealId);
}
Method apply in DealService has several methods in separate transactions (all methods doLogic() annotated with #Transactional(Propagation.REQUIRES_NEW):
public void apply(UUI dealId) {
someService1.do1Logic(...);
someService2.do2Logic(...);
someService3.do3Logic(...);
}
In do2Logic() I have some logic that adding new Rate entity to my parent entity with dealId and direct call of save method for Deal object.
#Transactional(Propagation.REQUIRES_NEW)
publid void do2Logic(...) {
...
var deal = dealService.getById(...);
deal.getRates().add(new Rate());
dealService.save(deal);
}
But when I get response from root method applyDeal the new child entity is absent.
If after that I will try to get this parent in separate rest call (getDeal) I get actual parent entity with new child in collection.
How to get actual child collection in parent response of applyDeal method?
I tried to make all logic in one #Transactional but it doesn't works.
I also don't understand why when I am try to get deal instance to return in applyDeal I get old data.
Thank you.
I guess you are running MySQL or MariaDB? These two database by default use the repeatable read transaction isolation level, which can cause this behavior. Try configuring the read committed isolation level instead, and/or remove the REQUIRES_NEW propagation if possible, since that will suspend an already running transaction to start a second one.

Spring Data JPA: deleteById does not delete record from database but derived delete method does

I'm observing a kind of strange behavior in my Spring application. Unfortunately I cannot share the complete code, but basically this is what it looks like:
// the repository
#Repository
public interface InboxRepo extends JpaRepository<Inbox, Long> {}
// the service
#Transactional
public void deleteInbox(long id) {
inboxRepo.deleteById(id);
}
When calling deleteInbox(), there is no exception or any kind of error but the Inbox item is not deleted from the database. Setting spring.jpa.show-sql=true shows that there isn't even a DELETE statement, i.e. for whatever reason, the code doesn't actually issue the deletion.
When defining a derived delete method in the repository, then the deletion works, but it doesn't yet make sense to me:
#Repository
public interface InboxRepo extends JpaRepository<Inbox, Long> {
// this seems to work
#Modifying
#Query("delete from Inbox i where i.id = ?1")
void delete(long id);
}
Dleting directly via an EntityManager also works. But what could be the reason that the "standard" JpaRepository methods don't work here?
I found the root cause. There was another entity having a reference to Inbox like this:
#OneToMany(mappedBy = "inbox", cascade = ALL, fetch = FetchType.EAGER)
private Set<Inbox> inbox = new HashSet<>();
The FetchType.EAGER in combination with the cascade caused the problem, i.e. as soon as the Inbox was deleted, this reference caused the Inbox to get "re-persisted". Setting FetchType.LAZY resolved the problem.

Spring Boot: H2 Not Actually Retrieving From Database

I'm trying to write a simple Repository test in Spring Boot. The Test code looks like this:
public class UserRepositoryTest {
private final TestEntityManager entityManager;
private final UserRepository userRepository;
#Autowired
public UserRepositoryTest(TestEntityManager entityManager, UserRepository userRepository) {
this.entityManager = entityManager;
this.userRepository = userRepository;
}
#Test
public void test() {
String firstName = "Frank";
String lastName = "Sample";
String email = "frank#example.com";
String username = "frank#example.com";
String password = "floople";
String passwordConfirm = "floople";
RegisterUserRequest registerUserRequest = new RegisterUserRequest(firstName, lastName, email, username, password, passwordConfirm);
User user = new User(registerUserRequest);
user.setSpinsRemaining(0);
userRepository.save(user);
userRepository.setSpinsRemainingToTen();
User found = userRepository.findByUsername(username);
assertThat(found.getSpinsRemaining()).isEqualTo(10);
}
What's I expect to happen is that the new User object is persisted to the database, the row in the database is modified to set spinsRemaining to 10, and then the now-modified row is retrieved from H2 and shoved into a new variable named "found". The "found" variable will point to an instance of a User object with ten spins remaining.
What actually happens is that the "found" variable points to the exact same instance of User that the "user" variable is. In fact, if I modify some property of the "user" variable AFTER persisting it to H2, the resultant "found" object also has the modified property. According to IntelliJ, both "user" and "found" are pointing to the same thing. How is that possible?
Hibernate caches entities inside a transaction in memory ("first level cache"). - Every time it retrieves an entity from database (or when it's asked to do so by the entity id) it will first look for it in cache so you don't have multiple instances of one entity with the same ID.
But in tests it's sometimes useful to have a "fresh" entity as it can uncover bugs in your persistance configuration/code. What you need to do:
Call EntityManager#flush - this will force synchronization of your changes to the database (save method does not guarantee that when called inside a transaction).
Call EntityManager#clear - Hibernate will forget about previous entity instances and will start fetching from DB again.
Alternatively: You can also instruct your Spring repository method to clear entities automatically after a modifying query. - But this will wipe out all entity instances and not only the one you are modifying so it might not be desirable in your application code.

How to use #Postconstruct in order to load some data

currently, I have an import.sql with which I import some test data in my database. Now, I want to bring it to our production system and what I read so far is that I should not use the import.sql in production.
Therefore, I thought I can create something with #Postconstruct.
I, therefore, created in the main application class something like that:
#Autowired
ICreateUserAtStartup repo;
#PostConstruct
public void initIt() throws Exception {
Rolle r = new Rolle();
r.setBezeichnung("xxx");
r.setId(1L);
r.setCreatedAt(new Date(2019, 01, 14));
repo.insertRolle(1L, "xxx");
}
In an seperate file I created the following interface:
#Repository
public interface ICreateUserAtStartup {
#Modifying
#Query("insert into benutzer(id, created_at, anzeigename,
benutzername, dienstnummer, active, passwort) SELECT :id,
:created_At, :anzeigename, :benutzername, :dienstnummer, :active,
:passwort")
void insertBenutzer(#Param("id") Long id, #Param("created_at")
String created_at, #Param("anzeigename") String anzeigename,
String benutzername, String dienstnummer, Boolean active, String password);
#Modifying
#Query("insert into rolle(id, bezeichnung) SELECT (:id,
:bezeichnung)")
void insertRolle(#Param("id") Long id, #Param("bezeichnung")
String bezeichnung);
}
However, as soon as I try to autowire repo in my main class, I always get the following exception:
No qualifying bean of type 'x.y.z.repository.ICreateUserAtStartup' available
Why don't you just use a specific migration tool for this purpose like Flyway or Liquibase?
https://flywaydb.org/
https://www.liquibase.org/
The reason why it cannot be autowired is that you haven't implemented that interface and created a bean from the implementation. Of course you might think that if you just create an interface and annotate it with #Repository, it will work out of the box but that's not the case.
If you want to use Spring Data for your repository layer, you'll need an entity and you need to extend at least CrudRepository.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories

Spring Boot Data JPA - Modifying update query - Refresh persistence context

I'm working with Spring Boot 1.3.0.M4 and a MySQL database.
I have a problem when using modifying queries, the EntityManager contains outdated entities after the query has executed.
Original JPA Repository:
public interface EmailRepository extends JpaRepository<Email, Long> {
#Transactional
#Modifying
#Query("update Email e set e.active = false where e.active = true and e.expire <= NOW()")
Integer deactivateByExpired();
}
Suppose we have Email [id=1, active=true, expire=2015/01/01] in DB.
After executing:
emailRepository.save(email);
emailRepository.deactivateByExpired();
System.out.println(emailRepository.findOne(1L).isActive()); // prints true!! it should print false
First approach to solve the problem: add clearAutomatically = true
public interface EmailRepository extends JpaRepository<Email, Long> {
#Transactional
#Modifying(clearAutomatically = true)
#Query("update Email e set e.active = false where e.active = true and e.expire <= NOW()")
Integer deactivateByExpired();
}
This approach clears the persistence context not to have outdated values, but it drops all non-flushed changes still pending in the EntityManager. As I use only save() methods and not saveAndFlush() some changes are lost for other entities :(
Second approach to solve the problem: custom implementation for repository
public interface EmailRepository extends JpaRepository<Email, Long>, EmailRepositoryCustom {
}
public interface EmailRepositoryCustom {
Integer deactivateByExpired();
}
public class EmailRepositoryImpl implements EmailRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
#Transactional
#Override
public Integer deactivateByExpired() {
String hsql = "update Email e set e.active = false where e.active = true and e.expire <= NOW()";
Query query = entityManager.createQuery(hsql);
entityManager.flush();
Integer result = query.executeUpdate();
entityManager.clear();
return result;
}
}
This approach works similar to #Modifying(clearAutomatically = true) but it first forces the EntityManager to flush all changes to DB before executing the update and then it clears the persistence context. This way there won't be outdated entities and all changes will be saved in DB.
I would like to know if there's a better way to execute update statements in JPA without having the issue of the outdated entities and without the manual flush to DB. Perhaps disabling the 2nd level cache? How can I do it in Spring Boot?
Update 2018
Spring Data JPA approved my PR, there's a flushAutomatically option in #Modifying() now.
#Modifying(flushAutomatically = true, clearAutomatically = true)
I know this is not a direct answer to your question, since you already have built a fix and started a pull request on Github. Thank you for that!
But I would like to explain the JPA way you can go. So you would like to change all entities which match a specific criteria and update a value on each. The normal approach is just to load all needed entities:
#Query("SELECT * FROM Email e where e.active = true and e.expire <= NOW()")
List<Email> findExpired();
Then iterate over them and update the values:
for (Email email : findExpired()) {
email.setActive(false);
}
Now hibernate knows all changes and will write them to the database if the transaction is done or you call EntityManager.flush() manually. I know this won't work well if you have a big amount of data entries, since you load all entities into memory. But this is the best way, to keep the hibernate entity cache, 2nd level caches and the database in sync.
Does this answer say "the `#Modifying´ annotation is useless"? No! If you ensure the modified entities are not in your local cache e.g. write-only application, this approach is just the way to go.
And just for the record: you don't need #Transactional on your repository methods.
Just for the record v2: the active column looks as it has a direct dependency to expire. So why not delete active completely and look just on expire in every query?
As klaus-groenbaek said, you can inject EntityManager and use its refresh method :
#Inject
EntityManager entityManager;
...
emailRepository.save(email);
emailRepository.deactivateByExpired();
Email email2 = emailRepository.findOne(1L);
entityManager.refresh(email2);
System.out.println(email2.isActive()); // prints false

Resources