Why does JPA modifying query require #Transactional annotation? - spring

Given the code below, why is it that when I call PersonService.updateAccountMembership from a controller I need to have the #Transactional annotation on the #Modifying query (AccountRepo.updateMembership)? Isn't having the #Transactional annotation on the service call that calls the modifying query sufficient (AccountService.updateMembership)?
The code below would break if I remove the #Transactional annotation on the modifying query with the following exception:
javax.persistence.TransactionRequiredException: Executing an update/delete query
Service:
#Service
public class PersonService{
#Autowired
PersonRepo personRepo;
#Autowired
AccountService accountService;
public Person updateAccountMembership(Person person, int membership){
person = this.save(person);
accountService.updateMembership(person, membership);
}
#Transactional
public Person save(Person person){
return personRepo.save(person);
}
}
Account Service:
#Service
public class AccountService {
#Autowired
AccountRepo accountRepo;
#Transactional
public void updateMembership(Person person, int membership){
accountRepo.updateMembership(person, membership);
}
}
Account Repo:
public class AccountRepo extends JpaRepository<Account,Integer> {
#Transactional //WHY IS THIS REQUIRED????????
#Modifying
#Query("update .........")
void updateMembership(#Param("person") Person person, #Param("memb") int membership);
}

Sure
You have two kinds of queries in sql. Read-Queries and Write-Queries.
A database can have multiple clients. What if two clients update the gender of a person at the same time? The last one wins!
This is an example using two methods: void buy() and void pay().
Client A read next empty invoice number 0000001
Client B read next empty invoice number 0000001
Client A change invoice0000001-payer to Max
Client A store invoice 0000001
Client B change invoice0000001-payer to Tom
Client B store invoice 0000001 <<--- crash! Already in use!
Problem: Max buyed it, but Tom payed it.
If you use transactions you can bind step 5 with step 6. If Step 6 failed, Step 5 is rolled back.
Your database across-the-board requires transations. You can not modify data without.

Related

Spring Data: can I create a method in the #Repository with a name what return list of object if String equals to constant?

Guess easier if I show you my example:
#Entity
class User {
Long id;
Status status;
}
enum Status {
NEW("N"), DELETED("D")
}
I have an AttributeConverter on Status so in DB the enum is stored with one character.
In my database I have entities like:
Table user
------------
Id Status
1 N
2 N
3 D
4 N
5 D
I want a method that list the Users with Status D. Something like this:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByStatusEqualsD();
or
List<User> findByStatusEqualsDeleted();
problem is these are not working
}
I could write this:
List<User> findByStatus(Status status);
And call it as repo.findByStatus(Status.DELETED) but I want a method what returns only the deleted users.
If I call it as repo.findByStatus(Status.NEW) then it will return the new users.
I prefer to not write a #Query, I hope it is possible what I'm asking without doing it...
Thanks in advance.
Such behavior is not supported.
Method name is translated into JPQL expression (which is the same as used in #Query) with parameters in it (if needed) so you have to provide these. (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation)
If you want query parameters to be hardcoded - #Query is what you need.
Alternatively you can have default method in your repository calling the parametrized one as mentioned here JpaRepository with Enum: findAllByOfferState_ACTIVE. NoSuchElementException
Easy,
You don't need a repo for that. Create a Service instead:
public interface UserDAOService{
List<User> getAllDeletedUsers();
}
And then just implement it with hardcoded findByStatus method from repo:
#Service
public class UserDAOServiceImpl implements UserDAOService{
private final UserRepository userRepository;
public UserDAOServiceImpl(UserRepository userRepository) {
this.userRepository= userRepository;
}
#Override
public List<Author> getAllDeletedUsers();
return userRepository.findByStatus(Status.DELETED);
}

How to solve save/persist entity in concurrent transaction and What is the best approach to handle concurrent requests?

How to solve save/persist entity in concurrent transaction and What is the best approach to handle concurrent requests?
The problem, being encountered is due to concurrency of both the book creation requests, both the instances check that book does not exist in the beginning, both try to create the book in the database, one succeeds other fails.
Let say we have one table with three columns
Table = BOOK
COLUMNS = ID, NAME, AUTHOR
Table also has unique constraint on name and author column.
CONSTRAINT uq_book UNIQUE (NAME, AUTHOR)
#NoRepositoryBean
public class BookRepository implements IBookRepository
{
#Autowired
ISpringJpaBookRepository springJpaBookRepository;
#Override
public Book createBook(Book book) throws Exception {
BookEntity bookEntity = springJpaBookRepository.getByNameAndAuthor(book.getName(), book.getAuthor())
//Create a book entity and save
BookEntity bookEntity = createBook(getBookEntity(book));
bookEntity = springJpaBookRepository.save(bookEntity);
Book bookCreated = getBookFromEntity(bookEntity);
return bookCreated;
}
}
public class BookService implements IBookService
{
#Autowired
IBookRepository bookRepository;
#Override
#Transactional
public Book createBook(Book book) throws Exception {
bookRepository .createBook(Book book);
}
}

How to make DataJpaTest flush save automatically?

I have an Employee entity with the following column:
#Entity
class Employee {
#Column(name = "first_name", length = 14)
private String firstName;
and I have a Spring JPA Repository for it:
#Repository
public interface EmployeeRepository extends CrudRepository<Employee, Integer> {
In test/resources/application.properties I have the following so that I use an in-memory h2 database with tables auto-generated:
spring.jpa.hibernate.ddl-auto=create
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa
I was expecting this test to fail, since the firstName is longer than what is allowed:
#DataJpaTest
public class EmployeeRepositoryTest {
#Autowired
private EmployeeRepository employeeRepository;
#Test
public void mustNotSaveFirstNameLongerThan14() {
Employee employee = new Employee();
employee.setFirstName("koraykoraykoray"); // 15 characters!
employeeRepository.save(employee);
}
}
And I was surprised to see this test was not failing, however the following does fail:
#DataJpaTest
public class EmployeeRepositoryTest {
#Autowired
private EmployeeRepository employeeRepository;
#Test
public void testMustNotSaveFirstNameLongerThan14() {
Employee employee = new Employee();
employee.setFirstName("koraykoraykoray"); // 15 characters!
employeeRepository.save(employee);
employeeRepository.findAll();
}
}
with the stacktrace:
Caused by: org.h2.jdbc.JdbcSQLDataException: Value too long for column "FIRST_NAME VARCHAR(14)": "'koraykoraykoray' (15)"; SQL statement:
The only difference is the second test has the additional employeeRepository.findAll(); statement, which forces Hibernate to flush as far as I understand.
This does not feel right to me, I would much rather want the test to fail immediately for save.
I can also have
#Autowired
private TestEntityManager testEntityManager;
and call
testEntityManager.flush();
but again, this does not feel correct either.. How do I make this test fail without any workaround or additional statements?
The easiest option in your case is configure #Transactional annotation, forcing to send database all changes in your tests (it can be used only in specific ones):
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.jupiter.api.Assertions.assertThrows;
#Transactional(propagation = Propagation.NOT_SUPPORTED)
#DataJpaTest
public class EmployeeRepositoryTest {
#Autowired
private EmployeeRepository employeeRepository;
#Test
public void mustNotSaveFirstNameLongerThan14() {
Employee employee = new Employee();
employee.setId(1);
employee.setFirstName("koraykoraykoray"); // 15 characters!
assertThrows(DataIntegrityViolationException.class, () -> {
employeeRepository.save(employee);
});
}
#Test
public void mustSaveFirstNameShorterThan14() {
Employee employee = new Employee();
employee.setId(1);
employee.setFirstName("koraykor"); // 8 characters!
employeeRepository.save(employee);
}
}
PD: I have added a simple Integer property as PK of Employee entity due to your repository definition.
You can see the results in the following picture:
You could use JpaRepository<T,ID> instead of CrudRepository<T,ID>. Something like:
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer>
Then you can use its saveAndFlush() method anywhere you need to send data immediately:
#Test
public void mustNotSaveFirstNameLongerThan14() {
Employee employee = new Employee();
employee.setFirstName("koraykoraykoray"); // 15 characters!
employeeRepository.saveAndFlush(employee);
}
And in code where you would like to have optimization you still can use save() method.
Thanks doctore for your answer, I had the similar problem as OP and your solution has helped. I decided to dig a little and figure out why it works, should someone else have this problem.
With #DataJpaTest annotated test class, your class implicitly becomes #Transactional with default propagation type Propagation.REQUIRED. That means every test method is also #Transactional with same default configuration. Now, all CRUD methods in CrudRepository are also #Transactional, but it has nothing to do with #DataJpaTest - they are transactional due to implementation. Whoa, that's a lot of transactions!
As soon as you annotate your whole class (or just a test method) with #Transactional(propagation = Propagation.NOT_SUPPORTED), your test method(s) are no longer #Transactional. However, inner methods of your test method(s), that is, CRUD operations from CrudRepository, remain transactional, meaning that they will have their own transaction scopes. Because of that, they will be committed to database immediately after execution, because by default (in Spring Boot, which users HikariCP connection pool), auto commits are turned on. Auto commits happen after every SQL query. And thus tests pass as you'd expect.
I like to visualize things, so here is the visualization of the whole process:
I hope this was helpful. URLs from the diagram:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Propagation.html
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions
https://docs.oracle.com/javase/tutorial/jdbc/basics/transactions.html#disable_auto_commit
https://github.com/brettwooldridge/HikariCP/blob/dev/src/main/java/com/zaxxer/hikari/HikariConfig.java#L126
https://dzone.com/articles/spring-boot-transactions-tutorial-understanding-tr (not from diagram, but explains transaction very well!)
The #Commit can do the job ( it was added since 4.2)
#Test
#Commit
public void mustNotSaveFirstNameLongerThan14() {
Employee employee = new Employee();
employee.setId(1);
employee.setFirstName("koraykoraykoray"); // 15 characters!
assertThrows(DataIntegrityViolationException.class, () -> {
employeeRepository.save(employee);
});
}

Trying to get related entities from H2 database in Java Spring Boot

I've just started learning Spring Boot and am using a H2 database, I've got mostly everything working but I'm running into trouble trying to make a slightly more complex request. I've got 2 tables 'User' and 'Purchase', and I want to create and end point that returns all purchases that contain a given users ID. This seems simple if I used an SQL join or some similar query but I have no idea how to implement one.
I have a repository (CrudRepository) for both user and purchases, and then a service for each that gets the relevant data from database. This works perfect for the basic needs such as get, getById, etc. But I have no idea how to specify queries such as join and what not.
public interface UserRepo extends CrudRepository<User, Integer> {}
public interface ReceiptRepo extends CrudRepository<Receipt, Integer> {}
#Service
public class UserService {
#Autowired
UserRepo userRepo;
public User getUser(int id) { return userRepo.findById(id).get(); }
}
#RestController
public class UserController {
#Autowired
UserService userService;
#GetMapping("/user/{id}")
private User getUser(#PathVariable("id") int id) {
return userService.getUser(id);
}
}
That's basically the set up for both entities, and I'm not sure where and how I'd write more specific queries. Any help would be greatly appreciated.
Yoy can use #Query() annotation in order to write query.
You need to declare a method in your repo and on that method you can put this annotation.
Eg:
#Query("SELECT u FROM User u WHERE u.status = 1")
Collection<User> findAllActiveUsers();
You can take some more idea about this from here

JPA Hibernate Spring Repository ensures transaction completes on save?

I am creating a simple spring application which is supposed to book seats in a seminar. Lets say Booking class looks like this
#Entity
#Table(name = "bookings")
#IdClass(BookingId.class)
public class Booking{
#Id
private Long seminarId;
#Id
private String seatNo;
// .. other fields like perticipant info
// .. getter setters
}
of course the BookingId class:
public class BookingId implements Serializable{
private static final long serialVersionUID = 1L;
private Long seminarId;
private String seatNo;
// .. constructors, getters, setters
}
And I have a repository
#Repository
public interface BookingsRepository extends JpaRepository<Booking, BookingId>{
}
in the controller when a booking request arrives I first check if a booking with same seminer id and seat number already exists, if it doesn't exist I create one
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<BaseCrudResponse> createNewBooking(#Valid #RequestBody NewBookingDao newBookingDao, BindingResult bindingResult){
logger.debug("Request for a new booking");
// .. some other stuffs
Booking newBooking = new Booking();
newBooking.setSeminarId(newBookingDao.getSeminarId());
newBooking.setSeatNumber(newBookingDao.getSeatNumber());
// .. set other fields
Booking existing = bookingsRepository.findOne(new BookingId(newBooking.getSeminarId(), newBooking.getSeatNumber());
if (existing == null)
bookingsRepository.save(newBooking);
return new ResponseEntity<>(new BaseCrudResponse(0), HttpStatus.CREATED);
}
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
Now what will happen if the save method of the repository didn't finish commiting transaction and another request already gets past the existence check ? There might be incorrect booking (the last commit will override the previous). Is this scenario likely to happen ? Will the repository ensures that it completes the transaction before another save call ?
Also is there any way to tell Jpa to throw some exception (for IntegrityConstraintException if the composite key (in this case seminerId and seatNumber) already exists ? Now in the present setting its just updating the row.
You can use javax.persistence.LockModeType.PESSIMISTIC_WRITE so other transactions except the one that got the lock cannot update the entity.
If you use spring-data > 1.6 you can annotate the repository method with #Lock :
interface BookingsRepository extends Repository<Booking, Long> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
Booking findOne(Long id);
}
For sure you need to handle the locking exception that may be thron in the controller.

Resources