So I am tidying up my small Spring project and I noticed for some reason the #OneToOne annotation is not doing its job for me which in turn causes issues in another model.
github link : https://github.com/eamonmckelvey/sports-app
Basically, I have a User model class, a team model class and a player model class.
I want only one user to be able to create one team, and one team to have many players.
However, I am able to add as many teams to my user as I want which is wrong.
All the answers provided require me to add a no arg constructor and a constructor for my users class, but when I do this I get an error in my registration from class.
Please help.
1. User Model
#Entity
#Data
#NoArgsConstructor(access= AccessLevel.PRIVATE, force=true)
#RequiredArgsConstructor
public class User implements UserDetails {
#OneToOne(cascade = CascadeType.ALL,mappedBy = "user")
private Team team;
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
private final String username;
private final String password;
//private final String fullname;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
2. Team Model
#Data
#Entity
#Table(name="User_Team")
public class Team implements Serializable {
#OneToOne(fetch= FetchType.LAZY)
private User user;
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
//#NotBlank(message="Team Name is required")
private String teamName;
//#NotBlank(message="Location is required")
private String location;
//#NotBlank(message="Nickname required")
private String nickName;
private String yearEstablished;
public Sport sport;
private Divison divison;
3. Team Controller
#Slf4j
#Controller
#SessionAttributes("Team")
public class TeamController {
private TeamRepository teamRepository;
public TeamController(TeamRepository teamRepository) {
this.teamRepository = teamRepository;
}
#Autowired
TeamRepository service;
#GetMapping("/team")
public String displayTeam(Model model) {
model.addAttribute("team", service.findAll());
return "/team";
}
#GetMapping("/addTeam")
public String showSignUpForm(User user) {
return "addTeam";
}
#PostMapping("/addTeam")
public String processOrder(#Valid Team team, BindingResult result, SessionStatus
sessionStatus,
#AuthenticationPrincipal User user, Model model) {
if (result.hasErrors()) {
return "addTeam";
}
team.setUser(user);
service.save(team);
model.addAttribute("team", service.findAll());
return "team";
}
4. Registeration Form
#Data
public class RegistrationForm {
private String username;
private String password;
//private String fullname;
public User toUser(PasswordEncoder passwordEncoder) {
return new User(
username, passwordEncoder.encode(password));
}
}
5. Registration Controller
#Controller
#RequestMapping("/register")
public class RegistrationController {
private UserRepository userRepo;
private PasswordEncoder passwordEncoder;
public RegistrationController( UserRepository userRepo,
PasswordEncoder passwordEncoder){
this.userRepo = userRepo;
this.passwordEncoder = passwordEncoder;
}
#GetMapping
public String registerForm(){
return "registration";
}
#PostMapping
public String processRegistration(RegistrationForm form){
userRepo.save(form.toUser(passwordEncoder));
return "redirect:/login";
}
6. user details class
#Service
public class UserRepositoryUserDetailsService implements
UserDetailsService {
private UserRepository userRepo;
#Autowired
public UserRepositoryUserDetailsService(UserRepository userRepo) {
this.userRepo = userRepo;
}
#Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
User user = userRepo.findByUsername(username);
if (user != null) {
return user;
}
throw new UsernameNotFoundException(
"User '" + username + "' not found");
}
1) One user can have one team. That means OneToOne relation between user and team. You need not to annotate both user and team with #OneToOne.Remove #OneToOne annotation from team model.
Changes required are:
User model:
#Entity
class User{
#Id
private String id;
#OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name = "team_id")
private Team team;
//other fields
}
Team Model:
#Entity
class Team{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String teamName;
//other field
}
2) For one team to have many players requires #OneToMany
So, I copied your code and did some changes. After the following changes your code works fine.
1) Drop final keyword from below fields in user class(initalizing them doesn't seems to be a great idea).
private final String username;
private final String password;
2) User and Team should not have same serialization version.
private static final long serialVersionUID = 1L;
3) After doing above corrections. Your code will give you the actual error "nested exception is javax.persistence.PersistenceException"
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1628)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
..............
..........
Caused by: org.postgresql.util.PSQLException: ERROR: syntax error at or near "user"
To avoid it do the following changes in your model :
Put #Table(name="users") in user model.
Following are the models:
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
private String username;
#OneToOne(mappedBy = "user")
private Team team;
public User() {
}
}
Team Model
#Table(name="teams")
public class Team {
#Id
private Long id;
#OneToOne
#MapsId
// or you can use
// #OneToOne
// #JoinColumn(name="user_id")
private User user;
private String teamName;
public Team() {
}
}
Follow the above code. It works fine for me. Test Controller to check:
#RequestMapping(value = "/test", method = RequestMethod.GET)
public ResponseEntity<?> test() {
User user = userRepository.findById(2l);
Team team = user.getTeam();
return new ResponseEntity(team, HttpStatus.OK);
}
}
I hope this will help you out.
There are several issues with your code:
#JoinColumn on the child side is missing. It's not even on the parent side. In the User entity you declare #OneToOne(cascade = CascadeType.ALL,mappedBy = "user"), but it is not mapped in the child class.
FetchType.LAZY does not give you much in terms of performance in one-to-one, since hibernate needs to check the database for existence of an object to know whether return a proxy or null.
You're saving a child entity in your TeamController: service.save(team);, but there is no cascading from Team to User.
Try the following mapping:
public class User implements UserDetails {
#OneToOne(cascade = CascadeType.ALL, mappedBy = "user")
private Team team;
// other fields
}
public class Team implements Serializable {
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "user_id")
private User user;
// other fields
}
And keeping both sides synchronized. Instead of:
team.setUser(user);
service.save(team);
Try the following code in your TeamController (you will have to autowire UserRepository):
team = service.save(team);
team.setUser(user);
user.setTeam(team);
userRepository.save(user);
Hey so i found a fix here for my code.
1. Team Controller
#GetMapping("/addTeam")
public String showSignUpForm(SessionStatus sessionStatus,
#AuthenticationPrincipal User user, Model model)
{
//if the user has already the team we should not let them add another
// one
//this is due to having one to one relationship
long userHasTeamCount = service.countAllByUser(user);
if (userHasTeamCount > 0) {
return "redirect:team";
}
return "addTeam";
}
2. Team model
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
3. User Model
Removed the OneToOne here as its not needed
4. Team repo
#Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
Team findAllById(Long id);
long countAllByUser(final User user);
}
Related
I have two classes User.java and Address.java and there is a one-to-one bi-directional mapping between them.
But when I try to get the address using the User class I get an "java.lang.StackOverflowError: null" exception.
The same thing happens when I try to get the User from the Address class.
User.java
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String email;
private String phone;
private String password;
private String imageUrl;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "address")
private Address address;
Address.java
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "address")
private User user;
private String country;
private String state;
private String city;
private String street;
private String pincode;
MainController.java
#Controller
public class MainController {
#Autowired
private UserDao userDao;
#Autowired
private AddressDao addressDao;
#RequestMapping("/test")
#ResponseBody
public String test() {
User user = new User();
user.setName("name");
user.setEmail("email");
user.setPhone("phone");
user.setPassword("password");
user.setImageUrl("imageUrl");
Address address = new Address();
address.setCountry("country");
address.setState("state");
address.setCity("city");
address.setStreet("street");
address.setPincode("123456");
user.setAddress(address);
userDao.save(user);
return "working";
}
#RequestMapping("/fetch")
#ResponseBody
public String fetch() {
User user = userDao.getById((long) 1);
System.out.println(user.getAddress());
return "working";
}
}
I am using the test() function to put data in the database and it is working fine.
database image
But when I call the fetch() function I am getting the following error
java.lang.StackOverflowError: null
at org.hibernate.proxy.pojo.BasicLazyInitializer.invoke(BasicLazyInitializer.java:58) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:43) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
at
Updated MainController.java
package com.demo.controller;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.demo.dao.AddressDao;
import com.demo.dao.UserDao;
import com.demo.entity.Address;
import com.demo.entity.User;
#Controller
public class MainController {
#Autowired
private UserDao userDao;
#Autowired
private AddressDao addressDao;
#RequestMapping("/test")
#ResponseBody
public String test() {
User user = new User();
user.setName("name");
user.setEmail("email");
user.setPhone("phone");
user.setPassword("password");
user.setImageUrl("imageUrl");
userDao.save(user);
Address address = new Address();
address.setCountry("country");
address.setState("state");
address.setCity("city");
address.setStreet("street");
address.setPincode("123456");
addressDao.save(address);
user.setAddress(address);
userDao.save(user);
return "working";
}
#RequestMapping("/fetch")
#ResponseBody
public String fetch() {
Optional<User> op = userDao.findById((long) 1);
User user = op.get();
// working
System.out.println(user.getName() + " " + user.getEmail() + " " + user.getPhone());
// java.lang.StackOverflowError:null
System.out.println(user.getAddress());
return "working";
}
}
TLDR: you aren't actually saving anything anywhere, but it's easy to fix. Here's my code and my explanation:
MainController.java:
#RestController
public class MainController {
private final UserRepository userRepository;
private final AddressRepository addressRepository;
public MainController(UserRepository userRepository, AddressRepository addressRepository){
this.userRepository = userRepository;
this.addressRepository = addressRepository;
}
#GetMapping("/test")
public String test() {
User user = new User();
user.setName("name");
user.setEmail("email");
user.setPhone("phone");
user.setPassword("password");
user.setImageUrl("imageUrl");
user = userRepository.save(user);
System.out.println("saved user");
Address address = new Address();
address.setCountry("country");
address.setState("state");
address.setCity("city");
address.setStreet("street");
address.setPincode("123456");
address = addressRepository.save(address);
System.out.println("saved address");
user.setAddress(address);
userRepository.save(user);
System.out.println("set user's address");
return "working";
}
#GetMapping("/fetch")
public String fetch() {
Optional<User> optionalUser = userRepository.findById((long) 1);
if(optionalUser.isPresent()){
User user = optionalUser.get();
System.out.println(user.getAddress());
boolean addressExists = addressRepository.existsById((long) 1);
System.out.println(addressExists);
System.out.println(user.getAddress().getCountry());
return "working";
}
System.out.println("Error: user with id 1 not found!");
return "failing";
}
}
User.java:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String email;
private String phone;
private String password;
private String imageUrl;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;
//getters and setters omitted for brevity
}
Address.java:
#Entity
#Table(name = "address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToOne(mappedBy = "address")
private User user;
private String country;
private String state;
private String city;
private String street;
private String pincode;
//getters and setters omitted for brevity
}
AddressRepository.java:
public interface AddressRepository extends CrudRepository<Address, Long> {
}
UserRepository.java:
public interface UserRepository extends CrudRepository<User, Long> {
}
UserDAO.java:
public class UserDAO {
private final String name;
private final String email;
private final String phone;
private final String imageUrl;
public UserDAO(User user) {
name = user.getName();
email = user.getEmail();
phone = user.getPhone();
imageUrl = user.getImageUrl();
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public String getPhone() {
return phone;
}
public String getImageUrl() {
return imageUrl;
}
}
A DAO has no connection to the database, it's intent is what the acronym stands for, simply to transfer data, and that's it. When you make a repository, you can stick your objects there by saving them in the repository. Notice that by extending the CrudRepository with correct generics, you don't even need to implement the methods yourself. The save method actually saves the POJO, and returns the saved version, which is why I did user = userRepository.save(user), which may seem counterintuitive at first, but it simply helps ensure that everything is as you expect. If you then want to send the UserDAO object as a response, you can create it using the user object that is returned from the database, maybe something like:
UserDAO dao = new UserDAO(userRepository.save(user));
Please take notice of what is happening inside the test method in MainController. First, we create the POJO User object and set its fields. Then we have to save it to the repository, it is only persisted after you call save method of the repository. Please note that the user object is saved again once it is updated with the address.
This is a very crude way to do things, it is best to create a service layer and do this there with the #Transactional annotation, which would mean that everything is rolled back in case something goes wrong inside a method annotated as #Transactional.
Also, using CascadeType.ALL may be not what you want, please refer to this answer.
Inside fetch method, I ensure that the user indeed exists, which is not guaranteed. To avoid 500 errors, it's important to have a fallback mechanism for when something doesn't work.
As a final side note, you shouldn't be storing raw passwords like that, you should at least use hashing with salt and pepper, or use one of the many available libraries for implementing such functionality (although it can be quite fun getting down and dirty with the code itself). You should also consider how much information you are revealing when something does go wrong, as you don't want to give away too much information which could be used to deanonimise a specific user, or even learn more about your code and the system architecture.
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 started building my first REST webservice in Java using Spring and JPA.
Now I'm trying to create sign-up service. I have no problem with sending a request containing all Entity fields what looks:
#AllArgsConstructor
#NoArgsConstructor
#Data
#Builder
#Entity
#Table(name = "users")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Enumerated(EnumType.STRING)
private Gender gender;
#Column(name = "email")
private String email;
#Column(name = "login")
private String login;
#Column(name = "password")
private String password;
#Column(name = "registration_date")
#CreatedDate
private LocalDateTime registrationDate;
#OneToMany(mappedBy = "bookOwner", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Book> bookList = new ArrayList<>();
}
But what to do in situation I want my registration form having only login, password and email fields and filling the rest user details would be optional - after confirmation of registration?
I consider using ModelMapper and creating separate classes for every form, but is there any better approach?
I solved problem by my own using mentioned ModelMapper. I paste my code. Can be useful if someone's interested. Didn't make tests, but my DB looks fine and no exceptions are thrown.
public class DTOMapper {
private static final ModelMapper MAPPER = new ModelMapper();
private DTOMapper(){}
public static <S, T> T map(S source, Class<T> targetClass){
return MAPPER.map(source, targetClass);
}
}
#Service
#Transactional
public class SignUpService {
private final UserRepository userRepository;
#Autowired
public SignUpService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User registerUser(SignUpForm form){
if(userRepository.findByLogin(form.getLogin())!=null){
throw new LoginAlreadyUsedException(form.getLogin());
}
if(userRepository.findByEmail(form.getEmail())!=null){
throw new EmailAlreadyUsedException(form.getEmail());
}
User user = DTOMapper.map(form, User.class);
User saved = userRepository.save(user);
return DTOMapper.map(saved, User.class);
}
}
#AllArgsConstructor
#NoArgsConstructor
#Data
#Builder
public class SignUpForm implements Serializable {
private static final long serialVersionUID = 1L;
#NotEmpty
#Size(min = 5)
private String login;
#NotEmpty
#Size(min = 7)
private String password;
//todo email validation
#NotEmpty
private String email;
}
#RestController
public class SignUpController {
private static final Logger log = LoggerFactory.getLogger(SignUpController.class);
#Autowired
private SignUpService signUpService;
#PostMapping(value = "/signup")
public ResponseEntity<?> addUser(#RequestBody #Valid SignUpForm form, BindingResult errors){
if(errors.hasErrors()){
throw new InvalidRequestException(errors);
}
signUpService.registerUser(form);
return new ResponseEntity<>(form, HttpStatus.CREATED);
}
}
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);
}