How to make DataJpaTest flush save automatically? - spring

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);
});
}

Related

Sprint Boot JPA Entity unit test

Let say I have a Spring Boot app with spring-boot-starter-data-jpa
I have an entity
Entity
#Table(name = "STUDENT")
#Getter
public class Student {
#Id
#Column(name="ID") private int id;
#Column(name="NAME") private String name;
#Column(name="DESCRIPTION") private String description;
}
and a repository
public interface StudentRepository extends JpaRepository<Student, Integer> {
Optional<Student> findByName(String name);
}
I understand that I do not have to unit test the repository as there is no code or custom query and that jpa is supposed to work.
How can I test that the table and column mapping is correct ?
Is it usefulness to test that ? Imagine in dev it's working fine but in acc the dba made a typo with the NAME column. If I do not test anything the application will start but will throw an exception when calling the findByName method.
I tried with something like
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class DocTypeRepositoryTest {
#Autowired
private StudentRepository studentRepository;
#Test
public void findByName() {
String name = "John";
docTypeRepository.findByName(name);
}
}
But is it a good practice ?
Another little question : with #DataJpaTest is it still a unit test or is it an integration test ?
Every external component outside of your application can be tested like that, but is this a unit test ? Certainly not anymore. It is an integration test. It verifies you are able to make some operations with the integrated system (databases, files, ESB, etc.) and they are working fine.
So if you make some typos errors, it is your responsability, not the responsability of the integrated components/systems you are using.
If integrated tests are usefullness, shame is in the air when your integrated component/system is evolving and your application is not able to do some tasks anymore. If you did not notice this bug in time, I bet your clients will be angry a lot.

#Before not setting up data during integration test

I am creating an integraiton test for a JpaRepository and the testcase fails with "Record not found with random value rand", as null is returned in the find results.
My test case:
#SpringBootTest
class JpatestsApplicationTests {
#Autowired
private JpaRepo jpaRepo;
#Before
void setup() {
FirstTable firstTable1 = new FirstTable();
firstTable1.setUid("x");
firstTable1.setRandom("rand");
jpaRepo.save(firstTable1);
}
#Test
void testFindByRandom() {
FirstTable f = jpaRepo.findByRandom("rand");//find by random value 'rand'
Assert.notNull(f, "Record not found with random value rand ");
}
The entity associated:
#Entity
#Table(name = "table1")
public class FirstTable {
#Id
#GeneratedValue
private String uid;
#Column
private String random;
And my Repository:
#Repository
public interface JpaRepo extends JpaRepository<FirstTable, Long> {
FirstTable findByRandom(String rand);
}
I am using h2 database.
Why is the result coming as null for findByRandom? Also please note that if I move the record saving part jpaRepo.save(firstTable1) to be within the test case (before the findByRandom("rand") is called, it gets passed.
Why wouldn't it work if I save the record in setup() method annotated with #Before ?
You have to add #Transactional on the top of your class.
#Transactional will cause your tests to execute within a test-managed transaction that will be rolled back after the test completes; code executed within the #Before method will be executed inside the test-managed transaction.
The latest version of spring-boot-test makes use of junit 5 and #Before is deprecated. It started working after changing to #BeforeAll with annotation of #TestInstance(TestInstance.Lifecycle.PER_CLASS) at the class level
My updated test class:
#SpringBootTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class JpatestsApplicationTests {
#Autowired
private JpaRepo jpaRepo;
#BeforeAll
void setup() {
FirstTable firstTable1 = new FirstTable();
firstTable1.setUid("x");
firstTable1.setRandom("rand");
jpaRepo.save(firstTable1);
}
#Test
void testFindByRandom() {
FirstTable f = jpaRepo.findByRandom("rand");//find by random value 'rand'
Assert.notNull(f, "Record not found with random value rand ");
}
Chose BeforeAll over BeforeEach as I need to run it only once during execution of this class.
Reference: https://junit.org/junit5/docs/current/user-guide/

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

Why does JPA modifying query require #Transactional annotation?

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.

OptimisticLockException not thrown when version has changed

I've created a simple EJB application that uses JPA for persistence and have a problem whereby optimistic locking is not functioning as I would have expected.
The application contains a class named Site which defines the model for a table named SITE in the database. The SITE table contains a column named ROW_VERSION which is referenced in the Site class using the #version annotation.
Whenever the record is updated, the ROW_VERSION is incremented by 1. So far, so good.
The problem arises when the row has changed in the time between the application reading the row using the EntityManager find method and the row being updated by the EntityManager merge method. As the ROW_VERSION for the row has been incremented by 1 and therefore is not the same as when the EntityManager find method was called, I would expect an OptimisticLockException to be thrown, but instead the changes are written to the table and in turn overwriting the changes made by the other process.
The application is running on WebSphere 8.5 and is using OpenJPA provided by the container.
Have I mis-understood how optimistic locking is supposed to work or is there something else that I need to do to make the OptimisticLockException occur?
The Site class is as follows:
#Entity
#Table(name="SITE")
public class Site {
#Id
#Column(name="SITE_ID")
private int id;
#Column(name="SITE_NAME")
private String siteName;
#Column(name="SITE_ADDRESS")
private String address;
#Column(name="ROW_VERSION")
#Version
private long rowVersion;
//getters and setters
}
The application makes uses of the Generic DAO wrapper class to invoke the EntityManager methods. The contents of the class are as follows:
public abstract class GenericDAO<T> {
private final static String UNIT_NAME = "Test4EJB";
#PersistenceContext(unitName = UNIT_NAME)
private EntityManager em;
private Class<T> entityClass;
public GenericDAO(Class<T> entityClass) {
this.entityClass = entityClass;
}
public T update(T entity) {
return em.merge(entity);
}
public T find(int entityID) {
return em.find(entityClass, entityID);
}
//other methods
}
Update - I've done some more investigation and have found this http://pic.dhe.ibm.com/infocenter/wasinfo/v8r0/index.jsp?topic=%2Fcom.ibm.websphere.nd.multiplatform.doc%2Finfo%2Fae%2Fae%2Fcejb_genversionID.html but even when I've added the #VersionColumn and #VersionStrategy annotations I still cannot get the OptimisticLockException to be thrown.

Resources