I have a function auto generate student_no using PostgreSQL. It working perfect when I use it in Postgres and Spring boot. But I have a problem when I use it in unit tests. In Postgres when I use the following command:
Insert into students(firstName,lastName,age)
values ('Nguyen','Tran',12)
in database student_no auto generator look like "28372". It is the same with Spring boot. When I call method: studentRepository.save(student) it autogenerates student_no. But when I the following UnitTest:
#Slf4j
public class StudentTest {
#Mock
private StudentRepository studentRepository;
#Test
public void testGenerateStudentBooking_validRequest() {
Student student = new Student("Nguyen","Tran",23);
Student student = studentRepository.save(student);
}
}
When I save and debug, student_no isn't generated. I can't understand why?
class Student:
#Entity
#NoArgsConstructor
#RequiredArgsConstructor
public class Student {
#Id
private String id;
private String firstName;
private String lastName;
private Integer age;
private String student_no;
}
StudentRepository
public inteface StudentRepository extends CrudRepository(Student,Integer) {
Student findStudentByStudentNo(String StudentNo);
}
Student test
#Slf4j
public class StudentTest {
#Mock
private StudentRepository studentRepository;
#Test
public void testGenerateStudentBooking_validRequest() {
Student student = new Student("Nguyen","Tran",23);
Student student = studentRepository.save(student);
//Student.studentNo is null :(
Student student1 = studentRepository.findStudentByStudentNo(student.getStudentNo);
}
}
if possible how can I tell hibernate that booking_no will be automatically generated and hibernate will not do anything with it. Or in other words how to automatically generate booking_no using the function in Postgres when using UnitTest.
There are two ways to solve your problem
You can use H2 in memory database for integration testing, so you don't need to mock StudentRepository
Second way is mocking the StudentRepository the one you are following now.
The problem in second approach is you need to define the stubbing for mocked objects saying that whenever StudentRepository.save() method is called return student object with randomly generated id
#Slf4j
public class StudentTest {
#Mock
private StudentRepository studentRepository;
#Test
public void testGenerateStudentBooking_validRequest() {
Random random = new Random();
when(this.studentRepository(ArgumentMatchers.any()))
.thenReturn(new Student("Nguyen","Tran",23,random.nextInt(900) + 100 ));
//random.nextInt(900) + 100 will generate a random integer from 0 to 899, then add 100
Student student = new Student("Nguyen","Tran",23);
Student student = studentRepository.save(student);
You can also use ThreadLocalRandom to generate random int value between 100(including) and 1000 (excluding)
ThreadLocalRandom.current().nextInt(100, 1000);
Related
I have a simple Repository:
public interface ReviewRepository extends CrudRepository<ReviewEntity, Integer> {
#Transactional(readOnly = true)
List<ReviewEntity> findByProductId(int productId);
}
I want to test it using test containers I followed the procedures and wrote my test case:
public abstract class MySqlTestBase {
private static MySQLContainer database = new MySQLContainer("mysql:5.7.32");
static {
database.start();
}
#DynamicPropertySource
static void databaseProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", database::getJdbcUrl);
registry.add("spring.datasource.username", database::getUsername);
registry.add("spring.datasource.password", database::getPassword);
}
}
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class PersistTests extends MySqlTestBase {
#Autowired
private ReviewRepository repository;
private ReviewEntity savedEntity;
#BeforeEach
void setupDb() {
repository.deleteAll();
ReviewEntity entity = new ReviewEntity(1, 2, "author1");
savedEntity = repository.save(entity);
assertEqualsReview(entity, savedEntity);
}
#Test
void update() {
savedEntity.setAuthor("author2");
repository.save(savedEntity);
ReviewEntity foundEntity = repository.findById(savedEntity.getId()).get();
assertEquals(1, (long)foundEntity.getVersion());
assertEquals("author2", foundEntity.getAuthor());
}
}
my ReviewEntity also is written like:
#Entity
public class ReviewEntity {
#Id #GeneratedValue
private int id;
#Version
private int version;
private int productId;
private int reviewId;
private String author;
public ReviewEntity(int productId, int reviewId, String author) {
this.productId = productId;
this.reviewId = reviewId;
this.author = author;
}
// setter and getter
}
When I run this test it fails at the assertEquals(1, (long)foundEntity.getVersion()); line with this message:
expected: <1> but was: <0>
Expected :1
Actual :0
But I update the ReviewEntity class and according to the documentation the #Version field should automatically increases but this not happens. what part of my test is wrong?
If you look at the default implementation of save method in CrudRepository interface in the SimpleJpaRepository class you will see save method is implemented like:
#Transactional
#Override
public <S extends T> List<S> saveAll(Iterable<S> entities) {
Assert.notNull(entities, "Entities must not be null!");
List<S> result = new ArrayList<S>();
for (S entity : entities) {
result.add(save(entity));
}
return result;
}
meaning it is marked with #Transactional with Required as its propagation level(it is default)
Required propagation works like this:
REQUIRED is the default propagation. Spring checks if there is an
active transaction, and if nothing exists, it creates a new one.
Otherwise, the business logic appends to the currently active
transaction
and for DataJpaTest annotation comment section says:
By default, tests annotated with #DataJpaTest are transactional and
roll back at the end of each test
So for method update in your test a transaction is going to be created and the save method in repository.save(savedEntity); is going to be appended to that transaction. meaning it is committed only if that transaction successfully committed and we now know that's not going to happen.
A workaround for this problem probably would be to annotate test class with #Transactional(propagation = NOT_SUPPORTED) to suspends the currently running transaction then for repository.save(savedEntity); a transaction is going to be created and committed at the end of save method and then you can proceed in your test.
I am using spring data and my DAO looks like
public interface StudentDAO extends JpaRepository<StudentEntity, Integer> {
public findAllOrderByIdAsc(); // I want to use some thing like this
}
In above code, commented line shows my intent. Can spring Data provides inbuilt functionality
to use such a method to find all records order by some column with ASC/DESC?
public interface StudentDAO extends JpaRepository<StudentEntity, Integer> {
public List<StudentEntity> findAllByOrderByIdAsc();
}
The code above should work. I'm using something similar:
public List<Pilot> findTop10ByOrderByLevelDesc();
It returns 10 rows with the highest level.
IMPORTANT:
Since I've been told that it's easy to miss the key point of this answer, here's a little clarification:
findAllByOrderByIdAsc(); // don't miss "by"
^
AFAIK, I don't think this is possible with a direct method naming query. You can however use the built in sorting mechanism, using the Sort class. The repository has a findAll(Sort) method that you can pass an instance of Sort to. For example:
import org.springframework.data.domain.Sort;
#Repository
public class StudentServiceImpl implements StudentService {
#Autowired
private StudentDAO studentDao;
#Override
public List<Student> findAll() {
return studentDao.findAll(sortByIdAsc());
}
private Sort sortByIdAsc() {
return new Sort(Sort.Direction.ASC, "id");
}
}
Simple way:
repository.findAll(Sort.by(Sort.Direction.DESC, "colName"));
Source: https://www.baeldung.com/spring-data-sorting
Please have a look at the Spring Data JPA - Reference Documentation, section 5.3. Query Methods, especially at section 5.3.2. Query Creation, in "Table 3. Supported keywords inside method names" (links as of 2019-05-03).
I think it has exactly what you need and same query as you stated should work...
Yes you can sort using query method in Spring Data.
Ex:ascending order or descending order by using the value of the id field.
Code:
public interface StudentDAO extends JpaRepository<StudentEntity, Integer> {
public findAllByOrderByIdAsc();
}
alternative solution:
#Repository
public class StudentServiceImpl implements StudentService {
#Autowired
private StudentDAO studentDao;
#Override
public List<Student> findAll() {
return studentDao.findAll(orderByIdAsc());
}
private Sort orderByIdAsc() {
return new Sort(Sort.Direction.ASC, "id")
.and(new Sort(Sort.Direction.ASC, "name"));
}
}
Spring Data Sorting: Sorting
I try in this example to show you a complete example to personalize your OrderBy sorts
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.*;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.data.domain.Sort;
/**
* Spring Data repository for the User entity.
*/
#SuppressWarnings("unused")
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
List <User> findAllWithCustomOrderBy(Sort sort);
}
you will use this example :
A method for build dynamically a object that instance of Sort :
import org.springframework.data.domain.Sort;
public class SampleOrderBySpring{
Sort dynamicOrderBySort = createSort();
public static void main( String[] args )
{
System.out.println("default sort \"firstName\",\"name\",\"age\",\"size\" ");
Sort defaultSort = createStaticSort();
System.out.println(userRepository.findAllWithCustomOrderBy(defaultSort ));
String[] orderBySortedArray = {"name", "firstName"};
System.out.println("default sort ,\"name\",\"firstName\" ");
Sort dynamicSort = createDynamicSort(orderBySortedArray );
System.out.println(userRepository.findAllWithCustomOrderBy(dynamicSort ));
}
public Sort createDynamicSort(String[] arrayOrdre) {
return Sort.by(arrayOrdre);
}
public Sort createStaticSort() {
String[] arrayOrdre ={"firstName","name","age","size");
return Sort.by(arrayOrdre);
}
}
Combining all answers above, you can write reusable code with BaseEntity:
#Data
#NoArgsConstructor
#MappedSuperclass
public abstract class BaseEntity {
#Transient
public static final Sort SORT_BY_CREATED_AT_DESC =
Sort.by(Sort.Direction.DESC, "createdAt");
#Id
private Long id;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
#PrePersist
void prePersist() {
this.createdAt = LocalDateTime.now();
}
#PreUpdate
void preUpdate() {
this.updatedAt = LocalDateTime.now();
}
}
DAO object overloads findAll method - basically, still uses findAll()
public interface StudentDAO extends CrudRepository<StudentEntity, Long> {
Iterable<StudentEntity> findAll(Sort sort);
}
StudentEntity extends BaseEntity which contains repeatable fields (maybe you want to sort by ID, as well)
#Getter
#Setter
#FieldDefaults(level = AccessLevel.PRIVATE)
#Entity
class StudentEntity extends BaseEntity {
String firstName;
String surname;
}
Finally, the service and usage of SORT_BY_CREATED_AT_DESC which probably will be used not only in the StudentService.
#Service
class StudentService {
#Autowired
StudentDAO studentDao;
Iterable<StudentEntity> findStudents() {
return this.studentDao.findAll(SORT_BY_CREATED_AT_DESC);
}
}
I'm using Redis to store students with entity:
#RedisHash("Student")
public class Student implements Serializable {
#Id
private Long id;
#Indexed
private String name;
private Integer age;
// getters
// setters
// Constructor with full parameters
}
and repository:
#Repository
public interface StudentRepository extends CrudRepository<Student, Long> {
}
I can save a list of students to Redis database and get that list without any error:
#Autowired
StudentRepository repo;
List<Student> students = new ArrayList<>();
Student student1 = new Student(........);
students.add(student1);
Student student2 = new Student(........);
students.add(student2);
repo.findAll().forEach(){
System.out.println(student);
}
The problem is when other project of mine (I'm building apps with micro-service architecture), I use findAll() function to get that list of students, it returns a list of two null elements. If I use findByName(String name), it still returns desired result.
Anyone who used to face this problem can help me, thank you in advanced ?
Turns out my Student class on the other project has the same #RedisHash("Student") but different full class name (same class name but different package). I think this issue belongs to the library.
Updated: cause #RedisHash doesn't work like expected, I found the way: that is adding #TypeAlias("Student") to the entity Student, therefore you can place Student anywhere in your source code
public class Student {
public int studentName;
public String Addr1;
public String Addr2;
public String Addr2;
//getter setter
}
I have one repository class which contains following methods
class StudentRepoImpl{
#Cacheable(value = "Students")
public List<Students> findAllStudents() {
//fetching all cust and putting in the Students cache
}
#Cacheable(value = "Students")
public List<Students> findStudentsBystudentNameAndAddresses() {
//fetching all cust data by **Name/Address1/Address2/Address3** basis of field available studentName/Address1/Address2/Address3 and putting in Student table
}
}
Current Output :
Fetch All data from DB and adding in Students cache in findAllStudents() method
But while searching for data based on some criteria (Name/Address1/Address2/Address3) using findStudentsBystudentNameAndAddresses() method it is fetching data from DB instead of cache.
Note: Not added Key while caching because there are 4 fields in search criteria (Name/Address1/Address2/Address3) and these are conditional fields means some time only Address1 will be in search criteria or sometime Address1+Address2 or sometimes all Address1+Address2+Address3 fields and I want to fetch exact match on the basis of Name and available Addresses.
did you add #EnableCaching annotation in your configuration class
Try to add configuration like this
#Configuration
public class CachingConfig {
#Bean(name = "springCM")
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("Students");
}
}
Hope useful
I am using spring data and my DAO looks like
public interface StudentDAO extends JpaRepository<StudentEntity, Integer> {
public findAllOrderByIdAsc(); // I want to use some thing like this
}
In above code, commented line shows my intent. Can spring Data provides inbuilt functionality
to use such a method to find all records order by some column with ASC/DESC?
public interface StudentDAO extends JpaRepository<StudentEntity, Integer> {
public List<StudentEntity> findAllByOrderByIdAsc();
}
The code above should work. I'm using something similar:
public List<Pilot> findTop10ByOrderByLevelDesc();
It returns 10 rows with the highest level.
IMPORTANT:
Since I've been told that it's easy to miss the key point of this answer, here's a little clarification:
findAllByOrderByIdAsc(); // don't miss "by"
^
AFAIK, I don't think this is possible with a direct method naming query. You can however use the built in sorting mechanism, using the Sort class. The repository has a findAll(Sort) method that you can pass an instance of Sort to. For example:
import org.springframework.data.domain.Sort;
#Repository
public class StudentServiceImpl implements StudentService {
#Autowired
private StudentDAO studentDao;
#Override
public List<Student> findAll() {
return studentDao.findAll(sortByIdAsc());
}
private Sort sortByIdAsc() {
return new Sort(Sort.Direction.ASC, "id");
}
}
Simple way:
repository.findAll(Sort.by(Sort.Direction.DESC, "colName"));
Source: https://www.baeldung.com/spring-data-sorting
Please have a look at the Spring Data JPA - Reference Documentation, section 5.3. Query Methods, especially at section 5.3.2. Query Creation, in "Table 3. Supported keywords inside method names" (links as of 2019-05-03).
I think it has exactly what you need and same query as you stated should work...
Yes you can sort using query method in Spring Data.
Ex:ascending order or descending order by using the value of the id field.
Code:
public interface StudentDAO extends JpaRepository<StudentEntity, Integer> {
public findAllByOrderByIdAsc();
}
alternative solution:
#Repository
public class StudentServiceImpl implements StudentService {
#Autowired
private StudentDAO studentDao;
#Override
public List<Student> findAll() {
return studentDao.findAll(orderByIdAsc());
}
private Sort orderByIdAsc() {
return new Sort(Sort.Direction.ASC, "id")
.and(new Sort(Sort.Direction.ASC, "name"));
}
}
Spring Data Sorting: Sorting
I try in this example to show you a complete example to personalize your OrderBy sorts
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.*;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.data.domain.Sort;
/**
* Spring Data repository for the User entity.
*/
#SuppressWarnings("unused")
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
List <User> findAllWithCustomOrderBy(Sort sort);
}
you will use this example :
A method for build dynamically a object that instance of Sort :
import org.springframework.data.domain.Sort;
public class SampleOrderBySpring{
Sort dynamicOrderBySort = createSort();
public static void main( String[] args )
{
System.out.println("default sort \"firstName\",\"name\",\"age\",\"size\" ");
Sort defaultSort = createStaticSort();
System.out.println(userRepository.findAllWithCustomOrderBy(defaultSort ));
String[] orderBySortedArray = {"name", "firstName"};
System.out.println("default sort ,\"name\",\"firstName\" ");
Sort dynamicSort = createDynamicSort(orderBySortedArray );
System.out.println(userRepository.findAllWithCustomOrderBy(dynamicSort ));
}
public Sort createDynamicSort(String[] arrayOrdre) {
return Sort.by(arrayOrdre);
}
public Sort createStaticSort() {
String[] arrayOrdre ={"firstName","name","age","size");
return Sort.by(arrayOrdre);
}
}
Combining all answers above, you can write reusable code with BaseEntity:
#Data
#NoArgsConstructor
#MappedSuperclass
public abstract class BaseEntity {
#Transient
public static final Sort SORT_BY_CREATED_AT_DESC =
Sort.by(Sort.Direction.DESC, "createdAt");
#Id
private Long id;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
#PrePersist
void prePersist() {
this.createdAt = LocalDateTime.now();
}
#PreUpdate
void preUpdate() {
this.updatedAt = LocalDateTime.now();
}
}
DAO object overloads findAll method - basically, still uses findAll()
public interface StudentDAO extends CrudRepository<StudentEntity, Long> {
Iterable<StudentEntity> findAll(Sort sort);
}
StudentEntity extends BaseEntity which contains repeatable fields (maybe you want to sort by ID, as well)
#Getter
#Setter
#FieldDefaults(level = AccessLevel.PRIVATE)
#Entity
class StudentEntity extends BaseEntity {
String firstName;
String surname;
}
Finally, the service and usage of SORT_BY_CREATED_AT_DESC which probably will be used not only in the StudentService.
#Service
class StudentService {
#Autowired
StudentDAO studentDao;
Iterable<StudentEntity> findStudents() {
return this.studentDao.findAll(SORT_BY_CREATED_AT_DESC);
}
}