I want to create an integration test for a repository method which will find a trainings of trainer between two dates to check in the next step if the proposed date of training will not overlapping with trainer time-table.
The main assumption in the application is that the User is the main entity and from it the account, training etc. are created accordingly. For this reason, the User is an active side in relation to the account and training.
The method looks like below:
#Query(
value =
"SELECT t FROM Training t INNER JOIN t.trainingParticipants p "
+ "WHERE p.trainer.id = :trainer_id "
+ "AND t.trainingDate.startDate <= :trainingStartDate "
+ "AND t.trainingDate.endDate <= :trainingEndDate ")
List<Training> findTrainingOfTrainer(
#Param("trainer_id") Long trainerId,
#Param("trainingStartDate") LocalDateTime trainingStartDate,
#Param("trainingEndDate") LocalDateTime trainingEndDate);
I am using this method in TrainingService like below:
#Service
#Slf4j
#Transactional
class TrainingService {
private final TrainingRepository trainingRepository;
private final UserRepository userRepository;
public TrainingService(TrainingRepository trainingRepository, UserRepository userRepository) {
this.trainingRepository = trainingRepository;
this.userRepository = userRepository;
}
public List<Training> findTrainingBetweenTwoDatesQuery(
Long trainerId, LocalDateTime startDate, LocalDateTime endDate) {
return trainingRepository.findTrainingOfTrainer(trainerId, startDate, endDate);
}
}
My User.java class looks like:
#Entity
#Table(name = "users")
#SuperBuilder
#AllArgsConstructor
#Getter
#Setter
public class User extends AbstractBaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "user_account_id", unique = true)
private Account account;
#Enumerated(EnumType.STRING)
private UserType userType;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private Set<Training> training;
#OneToOne(mappedBy = "trainer")
private TrainingParticipants trainingParticipants;
public User() {}
public User(Account account) {
this.account = account;
}
public Long getId() {
return id;
}
public Account getAccount() {
return account;
}
public UserType getUserType() {
return userType;
}
}
Training.java class looks like:
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Entity
#SuperBuilder
public class Training extends AbstractBaseEntity {
#Id
#GeneratedValue
private Long id;
#Column(name = "training_start_date")
LocalDateTime trainingStartDate;
#Column(name = "training_end_date")
LocalDateTime trainingEndDate;
#OneToMany(mappedBy = "training", cascade = CascadeType.ALL)
List<Exercise> exercises = new ArrayList<>();
#Column(name = "difficulty_level")
#Enumerated(EnumType.STRING)
private DifficultyLevel difficultyLevel;
#Column(name = "completed")
boolean completed;
#OneToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "training_participants_id", unique = true)
private TrainingParticipants trainingParticipants;
#Embedded private TrainingDate trainingDate;
#ManyToOne(cascade = CascadeType.PERSIST)
private User user;
}
My test class looks like:
#SpringBootTest
#ExtendWith(SpringExtension.class)
class TrainingServiceTest {
#Autowired private TrainingRepository trainingRepository;
#Autowired private UserRepository userRepository;
#Autowired private AccountRepository accountRepository;
#Autowired private TrainingService trainingService;
#BeforeEach
void setUp() {
trainingService = new TrainingService(trainingRepository, userRepository);
}
#Test
void shouldReturnTrainerTrainingBetweenTwoDates() {
LocalDateTime now = LocalDateTime.now();
userRepository.saveAll(userWithTrainingDataStub());
List<Training> trainingOfTrainer =
trainingRepository.findTrainingOfTrainer(1L, now.minusDays(7), now.plusDays(7));
assertEquals(4, userRepository.findAll().size());
assertEquals(4, trainingRepository.findAll().size());
assertEquals(2, trainingOfTrainer.size());
}
List<User> userWithTrainingDataStub() {
User user4 = userRepository.saveAndFlush(
User.builder()
.account(Account.builder().build())
.userType(UserType.TRAINER)
.build());
return List.of(
User.builder()
.account(Account.builder().build())
.userType(UserType.TRAINEE)
.training(
newHashSet(
Training.builder()
.trainingParticipants(
TrainingParticipants.builder()
.trainer(user4)
.build())
.trainingDate(
TrainingDate.builder()
.startDate(LocalDateTime.now().minusDays(3))
.endDate(LocalDateTime.now().minusDays(2))
.build())
.build()))
.build(),
User.builder()
.account(Account.builder().build())
.userType(UserType.TRAINEE)
.training(
newHashSet(
Training.builder()
.trainingParticipants(
TrainingParticipants.builder()
.trainer(user4)
.build())
.trainingDate(
TrainingDate.builder()
.startDate(LocalDateTime.now().minusDays(5))
.endDate(LocalDateTime.now().minusDays(3))
.build())
.build()))
.build(),
User.builder()
.account(Account.builder().build())
.userType(UserType.TRAINEE)
.training(
newHashSet(
Training.builder()
.trainingParticipants(
TrainingParticipants.builder()
.trainer(user4)
.build())
.trainingDate(
TrainingDate.builder()
.startDate(LocalDateTime.now().minusDays(5))
.endDate(LocalDateTime.now().minusDays(3))
.build())
.build()))
.build());
}
}
Problem is that first assertion passes and I am able to save 4 users but second assertion:
assertEquals(4, trainingRepository.findAll().size());
fails and my trainings are not saved while user creation. All I want to do is to be able to save my training while creating a user. I am pretty sure that there is a problem with mapping User <-> Training but honestly, I don't see any other potential fix to reach a desirable goal. I will be grateful for suggestions about how to fix my mapping and get a possibility to persist in training while user creation.
Related
I have two entities in my project User and Order, they are linked as ManyToOne, when I'm trying to get all Users with specific Order status I get multiple copies of the same User. What am I doing wrong here?
User.java
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "t_user")
public class User {
#Id
private UUID id;
#OneToMany(mappedBy = "user")
Set<Order> orders = new HashSet<>();
}
Order.java
public class Order {
#Id
private UUID id;
#Column(name = "order_status", nullable = false)
OrderStatus orderStatus;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
public void addUser(User user) {
this.user = user;
user.getOrders().add(this);
}
public void removeUser() {
if (user != null) {
user.getOrders().remove(this);
this.user = null;
}
}
}
OrderStatus.java
public enum OrderStatus {
PAYMENT, PAID, CAR_IN_USE, CAR_RETURNED, CLOSED;
}
UserDao.java
#Repository
public interface UserDao extends JpaRepository<User, UUID> {
Page<User> findAllByOrders_OrderStatus(OrderStatus orderStatus, Pageable pageable);
}
findAllByOrders_OrderStatus always return multiple copies of User (if I have 6 orders, I'll have 6 User copies). What should I change here?
I am making a small Rest API using Spring boot and mysql as database. I am getting "No serializer found" error with fetch type lazy. My application is working fine with fetch type eager but I want fetch type to lazy so how can I resolve this.
User Model:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(nullable = false,length = 50)
private String firstName;
#Column(nullable = false,length = 50)
private String lastName;
#Column(nullable = false,unique = true)
private String email;
#OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name = "address_id",referencedColumnName = "id")
#JsonIgnoreProperties("user")
private Address address;
}
Address Model:
#Entity
#Table(name = "addresses")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(nullable = false,length = 255)
private String street;
#Column(nullable = false)
private int postalCode;
#Column(nullable = false,length = 100)
private String city;
#OneToOne(mappedBy = "address",fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JsonIgnoreProperties("address")
private User user;
}
User Service:
#Service
public class UserService {
private final UserRepository userRepository;
#Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User save(User user){
return userRepository.save(user);
}
public List<User> find(){
return userRepository.findAll();
}
public User find(Integer id){
return userRepository.findById(id).get();
}
public void delete(Integer id){
userRepository.deleteById(id);
}
}
User Controller:
#RestController
#RequestMapping(path = "users")
public class UserController {
private final UserService userService;
#Autowired
public UserController(UserService userService) {
this.userService = userService;
}
#GetMapping
public List<User> get(){
return userService.find();
}
#GetMapping(path = "{id}")
public User get(#PathVariable Integer id){
return userService.find(id);
}
#PostMapping
public User post(#RequestBody User user){
return userService.save(user);
}
#DeleteMapping(path = "{id}")
public boolean delete(#PathVariable Integer id){
userService.delete(id);
return true;
}
}
Stack Trace:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.ArrayList[0]->com.kumasroh.usersapp.models.User["address"]->com.kumasroh.usersapp.models.Address$HibernateProxy$VM2Pif4w["hibernateLazyInitializer"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.13.1.jar:2.13.1]
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1300) ~[jackson-databind-2.13.1.jar:2.13.1]
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400) ~[jackson-databind-2.13.1.jar:2.13.1]
I think fetchType Lazy is used in OneToMany relationship on the otherhand fetchType Eager is used in OneToOne Relationship.
I'm new to the Spring boot JPA and struggling to find out the relationships between multiple entities.
I have a User Entity, a Product Entity, and a Review Entity.
A user has many reviews.
A product has many reviews.
A review has a product and a user.
At the moment, I'm using one-to-many relationships for user&reivew, product&review. However, the error occurred when deleting a review: ERROR: update or delete on table "users" violates foreign key constraint "fkcgy7qjc1r99dp117y9en6lxye" on table "reviews".
My question:
How can I delete a Review Entity without deleting the Product entity and User entity?
Which cascade type should I use?
User Entity:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "user_name")
private String userName;
#Column(name = "email")
private String email;
#Column(name = "password")
private String password;
#JsonManagedReference("reviews")
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY,
mappedBy = "user")
private List<Review> reviews = new ArrayList<>();
//constructor + getter+ setter
Product Entity:
#Entity
#Table(name = "products")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Float price;
#Transient
private Float rate;
private String category;
private String brand;
#JsonManagedReference("reviews")
#JsonIgnore
#OneToMany(mappedBy = "product")
List<Review> reviews = new ArrayList<>();
//constructor + getter+ setter
Review Entity:
#Entity
#Table(name = "reviews")
public class Review {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Float rate;
private String comment;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id",referencedColumnName = "id")
#JsonBackReference("user")
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JsonBackReference("product")
#JoinColumn(name = "product_id",referencedColumnName = "id")
private Product product;
//constructor + getter+ setter
User Controller:
#CrossOrigin(origins = "http://localhost:3000")
#RestController
#RequestMapping(path="users/")
public class UserController {
private final UserService userService;
#Autowired
public UserController(UserService userService) {
this.userService = userService;
}
...
#DeleteMapping("{userid}")
public User deleteUser(#PathVariable("userid") Long userid){
return userService.deleteById(userid);
}
}
User service:
#Service
public class UserService {
private final UserRepository userRepository;
private final ReviewRepository reviewRepository;
//dependency injection
#Autowired
public UserService(UserRepository userRepository, ReviewRepository reviewRepository) {
this.userRepository = userRepository;
this.reviewRepository =reviewRepository;
}
...
public User getUserById(Long id){
return userRepository.findById(id).orElseThrow(()->
new UserNotFoundException(id));
}
public User deleteById(Long id){
User user = getUserById(id);
userRepository.delete(user);
return user;
}
}
Simple run:
#SpringBootApplication
public class GroceryShoppingAppApplication {
public static void main(String[] args) {
ConfigurableApplicationContext configurableApplicationContext =
SpringApplication.run(GroceryShoppingAppApplication.class, args);
UserRepository userRepository = configurableApplicationContext.getBean(UserRepository.class);
ProductRepository productRepository =configurableApplicationContext.getBean(ProductRepository.class);
ReviewRepository reviewRepository = configurableApplicationContext.getBean(ReviewRepository.class);
User debbi= new User("Debbi","debbi#gamil.com","password");
Product apple = new Product("Apple",(float)3.40,"Fruit","Gala");
Product milk = new Product("Milk",(float)5.22,"Dairy","Anchor");
Review review1 = new Review(debbi,(float)4.5,"Good taste",apple);
Review review2 = new Review(debbi,(float)5.0,"Good milk",milk);
productRepository.save(apple);
productRepository.save(milk);
userRepository.save(debbi);
reviewRepository.save(review1);
reviewRepository.save(review2);
I think I should not use casacadeType.All because when deleting a user, I shouldn't delete the product in the review. I tried other types, the error still remains. Thus, currently I didn't use any casacadeType and need to save each entity one by one.
Please help me with this.
You are getting an error because the user in the review model does not have a referenced Column value.
Try this code:
#JoinColumn(name = "user_id",referencedColumnName = "id")
I have two Entities. One is UserEntity and other is TaskEntity.
#Entity
#Table(name="user")
public class UserEntity {
#Id
private String userEmail;
private String password;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="user_email")
private List<TaskEntity> tasks;
//getter setter for variables
}
#Entity
#Table(name="task")
public class TaskEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String description;
private String statusDate;
private String status;
//getter setter for variables
}
Now I want to create a new task based on userEmail, so this I am doing as follow in DAO class:
#PersistenceContext
EntityManager em;
public Integer addNewTaskByUserEmail(Task task, String userEmail) {
UserEntity userEntity = em.find(UserEntity.class, userEmail);
TaskEntity taskEntity = new TaskEntity();
taskEntity.setName(task.getName());
taskEntity.setDescription(task.getDescription());
taskEntity.setStatus(task.getStatus());
taskEntity.setStatusDate(task.getDate());
userEntity.getTasks().add(taskEntity);
return taskEntity.getId();
}
But in the return statement of I am getting null in service class. How can I return the auto-generated taskId?
A possible issue is you are not saving a task associated with the user. Save the task and then you might be able to get the taskId.
public Integer addNewTaskByUserEmail(Task task, String userEmail) {
UserEntity userEntity = em.find(UserEntity.class, userEmail);
TaskEntity taskEntity = new TaskEntity();
taskEntity.setName(task.getName());
taskEntity.setDescription(task.getDescription());
taskEntity.setStatus(task.getStatus());
taskEntity.setStatusDate(task.getDate());
em.getTransaction().begin();
em.persist(taskEntity);
em.getTransaction().commit();
userEntity.getTasks().add(taskEntity);
return taskEntity.getId();
}
OR
#Autowired TaskRepository taskRepository
public Integer addNewTaskByUserEmail(Task task, String userEmail) {
UserEntity userEntity = em.find(UserEntity.class, userEmail);
TaskEntity taskEntity = new TaskEntity();
taskEntity.setName(task.getName());
taskEntity.setDescription(task.getDescription());
taskEntity.setStatus(task.getStatus());
taskEntity.setStatusDate(task.getDate());
taskEntity = taskRepository.save(taskEntity)
userEntity.getTasks().add(taskEntity);
return taskEntity.getId();
}
Where TaskRepository will be
#Repository
public interface TaskRepository extends JpaRepository<TaskEntity, Integer>
{
}
I have an issue with the JPA relationship within a MVC SpringBoot application.
I'm trying to create an application where I have a number of users and every user can have a number of cars. A user can have multiple cars. I've done a #OneToOne relationship between the user and the car object, here's what I've done:
here is the User class:
#Entity
#Table(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "username", nullable = false)
private String username;
#Column(name = "password", length = 500, nullable = false)
private String password;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Car> cars;
}
then here is the Car class:
#Entity
#Table(name = "car")
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(length = 11)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "make", nullable = false)
private String make;
#Column(name = "model", nullable = false)
private String model;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "id")
private User user;
}
Then, here is the actual service implementation
#Component
#Transactional(readOnly = true)
public class CarServiceImpl implements CarService {
#Inject
private CarRepository carRepository;
#Inject
private UserRepository userRepository;
#Override
#Transactional(readOnly = false)
public Car addCar(Long userId, Car car) {
User user = userRepository.findOne(userId);
user.getGpsLocationModels().add(car);
car.setUser(user);
carRepository.save(car);
return car;
}
then I have the endpoint but that works fully. The add method looks like does work as supposed to, at least I get the expected output, however the find method I have no idea how to write it, well can't figure it out how to retrieve cars based on user, I know how to get them by their ID but not for every user separately.
Here is my try:
#Override
public Car findCar(Long userId, Long carId) {
//get the current user (that comes as JSON Request Param)
User user = userRepository.findOne(userId);
//get the car based on its ID, here's the problem, I want the car based on its ID AND its user, I can't display cars which aren't for that particular user
Car car = carRepository.findOne(carId);
return car;
}
Here is the method for get all cars for a particular user:
#Override
public List<Car> displayAllCars(Long userId) {
return userRepository.findOne(userId).getCars();
}
I'd really appreciate any help that you could advise.
Thank you so much
Your mappings are incorrect. Car > User is #ManyToOne. If you also make this bi-directional you can also then retrieve the cars via the user:
#Entity
#Table(name = "user")
public class User implements Serializable {
#OneToMany(mappedBy ="user",cascade = CascadeType.ALL)
private Set<Car> cars;
public Set<Car> getCars(){
return cars;
}
public void addCar(Car car){
cars.add(car);
car.setUser(this);
}
}
#Entity
#Table(name = "car")
public class Car implements Serializable {
#ManyToOne(fetch=FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="user_id")
private User user;
}
#Override
public Set<Car> findCars(Long userId) {
return userRepository.findOne(userId).getCars();
}
You could have a method that accepts the user ID and returns the list in the car repository like:
List<Car> findCarByUser(Long userID);
And then you will have
#Override
public List<Car> displayAllCars(Long userId) {
return carRepository.findCarByUser(userId);
}