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.
Related
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);
});
}
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
I created a basic graphql-java app with the spring boot starter and using the graphql spqr library against an MSSQL database utilizing Hibernate and Jpa.
I have an entity called "Task" with 5 fields. I have a simple Jpa repository and a simple Jpa service that calls a "findAllTasks" method. It works great, but if I specify, for example, only one field to query with graphiql, I can see through my SQL log that the select command executed is querying for ALL fields in my Task entity/table, rather than the one I want. Is this expected? I thought graphql only selects the fields you specify in the query command?
Here is my code:
Entity
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class Task {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#Column
public String desc;
#Column
public LocalDateTime createdOn;
#Column
public LocalDateTime modifiedOn;
#Column
public String owner;
}
Repository
#Repository
public interface TaskRepository extends JpaRepository<Task, Long> {}
Service
#GraphQLApi
#Service
public class TaskService {
private TaskRepository taskRepo;
#Autowired
public TaskService(TaskRepository taskRepo) {
this.taskRepo = taskRepo;
}
#GraphQLQuery
public List<Task> findAllTasks() {
return taskRepo.findAll();
}
}
When I run the following in graphiql:
query {
findAllTasks {
id
}
}
I get the following SQL statement that was generated from my log:
select task0_.id as Task1_1_0_. task0_.desc as Task1_2_0, task0_.createdOn as Task1_3_0, task0_.modifiedOn as Task1_4_0, task0_.owner as Task1_4_0 from Task as task0_
You have to make a distinction between your GraphQL API and your database. You defined a query method GraphQL that is called findAllTasks. In consequence, when you call this GraphQL query with any number of fields, it will call the Java method implementation findAllTasks.
You can see that the implementation of this Java method calls taskRepo.findAll(). Therefore, you will fetch all data from your tasks in database.
GraphQL will then filter the data from the tasks fetched by your Java method to match what is asked in the GraphQL query.
In a nutshell, GraphQL is in charge in returning just the fields that you requested, but your implementation is in charge of getting the data from the database.
Disclaimer: I'm not an expert of graphql-spqr, so the upcoming information might not work in your case (as it applied to graphql-java).
If you feel that your implementation is however not efficient enough (here we are really talking about efficiency), you could look into dataloaders.
I seem to be baffled on how JPA Repositories are suppose to work.
In a nut-shell
#Entity
public class User extends AbstractEntity {
protected final static String FK_NAME = "USER_ID";
#Column(nullable = false)
private String firstName;
#OneToMany(cascade = ALL, fetch = FetchType.LAZY, orphanRemoval = true)
#JoinColumn(name = "userId")
private List<Detail> details = new ArrayList<Detail>();
}
#Entity
public class Detail extends AbstractEntity {
Long userId;
String hello;
}
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByFirstName(#Param("firstName") String firstName);
}
And here is the only controller in the app:
#RestController
public class Home {
#Autowired
UserRepository userRepository;
#Autowired
DetailsRepository loanRepository;
#RequestMapping(value = "")
public HttpEntity home() {
User user = userRepository.findByFirstName("John");
if (user == null) {
user = new User();
user.setFirstName("John");
}
Detail detail = new Detail();
detail.setHello("Hello Msh");
user.getDetails().add(detail);
userRepository.save(user);
return new ResponseEntity("hi", HttpStatus.OK);
}
}
Below a screenshot from debugging session where the app just started and the get request to home() method creates new user, new detail, adds detail to user.
Below example - when the user is saved, the detail entity gets updated
Now on the next request, the old user John is found and has been added a new instance of detail.
The old user has been saved but now the newly created detail does not get updated outside.
How come this only works first time ?
Basically theres so much fail going on so that I would advise you to go a step backwards. If youre wana go the short path of getting a solution for exactly this problem continue reading ;)
First part related to the answer of Jaiwo99:
As I can see in the gradle view of intellij, your using Spring Boot. So it is necessary to place #EnableTransactionManagement on top of your configuration class. Otherwise the #Transacion annotation does not have any effect.
Second part your JPA/Hibernate model mapping. Theres so much bad practise on the net that it is no wonder that most beginners have troubles starting with it.
A correct version could look like (not tested)
#Entity
public class User extends AbstractEntity {
#Column(nullable = false)
private String firstName;
#OneToMany(cascade = ALL, fetch = FetchType.LAZY, orphanRemoval = true, mappedBy="user")
private List<Detail> details = new ArrayList<Detail>();
public void addDetail(Detail detail) {
details.add(detail);
detail.setUser(user);
}
}
#Entity
public class Detail extends AbstractEntity {
#ManyToOne
private User user;
private String hello;
public void setUser(User user){
this.user = user;
}
}
Some general advice related to creating a model mapping:
avoid bi-directional mappings whenever possible
cascade is a decision made on the service level and not at the model level and can have huge drawbacks. So for beginners avoid it.
I have no idea why people like to put JoinColumn, JoinTable and whatever join annotation on top of fields. The only reason to do this is when you have a legacy db (my opinion). When you do not like the names created by your jpa provider, provide a different naming strategy.
I would provide a custom name for the user class, because this is in some databases a reserved word.
Very simple, the first time you saved a new entity outside of hibernate session, the second time, the user object you got is a detached object, by default hibernate will not consider it is changed in this case.
*solution *
Move this logic to another service class, which annotated with #transactional
Or
Annotate your controller with transactional
Or
Override equals and hashCode method on user class may also help
Am new to Spring Boot & JPA...
Let's say I have two entities mapped to two tables which are joined in a database.
Student-1------<-Course
Also, lets presume that the database is already created and populated.
This depicts that one student has many courses...
My Student Entity:
#Entity
public class Student {
#OneToMany(mappedBy="student")
private List<Courses> courses;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "Student_Id")
private long studentId;
#Column(name = "Student_Name")
private String studentName;
protected Student() { }
// Getters & Setters
}
My Course Entity:
#Entity
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "Course_Id")
private long courseId;
#Id
#Column(name = "Student_Id")
private long studentId;
#ManyToOne
#PrimaryKeyJoinColumn(name="Student_Id", referencedColumnName="Student_Id")
private Student student;
#Column(name = "Course_Name")
private String courseName;
// Getters & Setters
}
In Spring Boot's Tutorial Guides, it illustrates how to extend a CrudRepository interface, but
it doesn't specify how to setup a Spring based DAO which contains custom finder methods which use HQL and EntityManager inside it.
Is the following DAO and DaoImpl correct?
public interface CourseDao {
List<Course> findCoursesByStudentName(String studentName);
}
#Repository
public class CourseDaoImpl implements CourseDao {
#PersistenceContext
EntityManager em;
public List<Course> findCoursesByStudentName(String studentName) {
String sql = "select c.courseName" +
"from Course c, Student s " +
"where c.course_id = s.student_id " +
"and s.studentName = :studentName ";
Query query = em.createQuery(sql);
query.setParameter("studentName", studentName);
return query.getResultList();
}
}
And then in the client code, for example, in the main class:
public class Application {
#Autowired
CustomerDao dao;
public static void main (String args []) {
List<Course> courses = dao.findCoursesByStudentName("John");
}
}
Is this the standard way to use HQL inside Spring DAOs ? I've seend examples of the #Transactional annotation being prepended to the DAO class's impl (e.g. CustomerDAOImpl) ?
Please let me know if this is the write way to structure my Spring Boot app or am I supposed to extend / add to the CrudRepository only?
If someone could correct my example and point me to a URL which talks about HQL using Entities that are joined, I would be very grateful.
The Spring Boot guides didn't depict joins or DAOs - I just need to learn how to properly create finder methods which emulate select statement which return lists or data structures.
Thanks for taking the time to read this...
If I understood your question correct you do have two questions:
How to create a DAO and DAOImpl?
Where to put your Transaction annotations?
In regards to the first question I want to point out that this is a question in regards to spring-data-jpa using Hibernate as a JPA provider, not spring-boot.
Using Spring Data I usually skip completely to create a DAO but directly use a Custom Repository extending a standard one like CrudRepository. So in your case you don't even have to write more code than:
#Repository
public interface StudentRepository extends CrudRepository<Student, Long> {
List<Student> findByStudentName(String studentName);
}
Which will be sufficient and Spring Data will take care of filling it with the correct implementation if you use
#Autowired
StudentRepository studentRepo;
in your service class. This is where I also usually annotate my methods with #Transactional to make sure that everything is working as expected.
In regards to your question about HQL please look into the spring data jpa documentation, which points out that for most of the cases it should be sufficient to stick to proper named methods in the interface or go for named queries (section 3.3.3) or use the #Query annotation (section 3.3.4) to manually define the query, e.g. should work (didn't tried):
#Repository
public interface #CourseRepository extends CrudRepository<Course, Long> {
#Query("select c.courseName from Course c, Student s where c.course_id = s.student_id and s.studentName = :studentName")
public List<Course> findCoursesByStudentName(String studentName);
}
If you annotate your CourseDaoImpl with #Transactional (Assuming your have defined JpaTransactionManager correctly) You can just retrieve the Student with the matching name and call the getCourses() method to lazy load the Courses attached to that student. Since findCoursesByStudentName will run within a Transaction it will load the courses just fine.
#Repository
#Transactional(readOnly=true)
public class CourseDaoImpl implements CourseDao {
#PersistenceContext
EntityManager em;
public List<Course> findCoursesByStudentName(String studentName) {
String sql = "select s " +
"from Student s " +
"where s.studentName = :studentName ";
Query query = em.createQuery(sql);
query.setParameter("studentName", studentName);
User user = query.getSingleResult();
if(user != null) {
return user.getCourses();
}
return new ArrayList<Course>();
}
}