I have basicly user and role entities in my project.
#Entity
#Table(name="`User`")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String username;
private String password;
#ManyToMany(fetch = EAGER)
private Collection<Role> roles = new ArrayList<>();
}
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToMany(mappedBy = "roles")
private List<User> users= new ArrayList<User>();
}
This is my controller class
#RestController
#RequestMapping("/api")
#RequiredArgsConstructor
public class UserResource {
private final UserService userService;
#GetMapping("/users")
public ResponseEntity<List<User>> getUsers(){
return ResponseEntity.ok().body(userService.getUsers());
}
And this is my UserManager class
#Service #RequiredArgsConstructor #Transactional #Slf4j
public class UserManager implements UserService {
private final UserRepo userRepo;
private final RoleRepo roleRepo;
#Override
public List<User> getUsers() {
log.info("Fetching all users {}" );
return userRepo.findAll() ;
}
}
When I request http://localhost:8080/api/users, I get wrong data like this
[{"id":5,"name":"Emirhan Ay","username":"emrhn1888","password":"1234","roles":[{"id":1,"name":"ROLE_USER","users":[{"id":5,"name":"Emirhan Ay","username":"emrhn1888","password":"1234","roles":[{"id":1,"name":"ROLE_USER","users":[{"id":5,"name":"Emirhan Ay","username":"emrhn1888","password":"1234","roles":[{"id":1,"name":"ROLE_USER","users":[{"id":5,"name":"Emirhan Ay","username":"emrhn1888","password":"1234","roles":[{"id"}]}]}]}]
But the data saved in the db is like this
Where is my mistake? Thanks in advance for your answers.
You have 'circular dependency'. User has roles, the role has users, etc. You should probably first map entities to DTOS and then maybe add #JsonManagedReference or #JsonBackReference.
Or you can simply put #JsonIgnore on private List<User> users= new ArrayList<User>();
Related
I have make the rest api using Spring boot. It has two entities employee and department. Employee has department object so relation is ManyToOne. It is unidirectional. Department do not have the employees list. When I hit the employees GET method endpoint I am getting com.fasterxml.jackson.databind.exc.InvalidDefinitionException Exception. Please tell me how to solve it.
Employee Class:
#Entity
#Table(name = "employees")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String firstName;
private String lastName;
private Integer salary;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="department_id",referencedColumnName = "id",nullable = false)
private Department department;
Department Class:
#Entity
#Table(name = "departments")
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
Employee Service:
#Service
public class EmployeeService {
private final EmployeeRepository employeeRepository;
#Autowired
public EmployeeService(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
public List<Employee> getAllEmployees(){
return employeeRepository.findAll();
}
public Employee getEmployeeById(Integer id){
Optional<Employee> optionalEmployee = employeeRepository.findById(id);
if(optionalEmployee.isEmpty()){
throw new IllegalStateException("Department does not exists");
}
return optionalEmployee.get();
}
public Employee createEmployee(Employee employee){
return employeeRepository.save(employee);
}
Employee Controller:
#RestController
#RequestMapping(path = "/employees")
public class EmployeeController {
private final EmployeeService employeeService;
#Autowired
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
#GetMapping
public List<Employee> getAllDepartments(){
return employeeService.getAllEmployees();
}
#PostMapping
public Employee createEmployee(#RequestBody Employee employee){
return employeeService.createEmployee(employee);
}}
You have to put #JsonProperty(access = JsonProperty.Access.WRITE_ONLY) annotation in ManyToOne relationship mapping
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 am having some problems with Hibernate and the one-to-one mapping.
My DTO class is like this:
CustomerDTO
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
public class CustomerDTO {
private String nic;
private String name;
private String address;
private String contact;
private ArrayList<UserDTO> user = new ArrayList<>();
UserDTO
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
public class UserDTO {
private String email;
private String password;
private String role;
private String lastLogged;
}
My entity class is like this
Customer
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Customer {
#Id
private String nic;
private String name;
private String address;
private String contact;
#OneToOne(mappedBy = "customer", cascade = CascadeType.ALL)
private User user;
User
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class User {
#Id
private String email;
private String password;
private String role;
private String lastLogged;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "cusNIC", referencedColumnName = "nic", nullable = false)
private Customer customer;
}
CustomerControllerClass
#RestController
#RequestMapping("/api/v1/customer")
#CrossOrigin
public class CustomerController {
#Autowired
CustomerService customerService;
#PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity saveCustomer(#RequestBody CustomerDTO dto){
customerService.saveCustomer(dto);
StandradResponse success = new StandradResponse(200, "success", null);
return new ResponseEntity(success, HttpStatus.OK);
}
}
CustomerService class
#Transactional
#Service
public class CustomerServiceImpl implements CustomerService {
#Autowired
CustomerRepo customerRepo;
#Autowired
UserRepo userRepo;
#Autowired
ModelMapper mapper;
#Override
public void saveCustomer(CustomerDTO dto) {
if(!customerRepo.existsById(dto.getNic())){
Customer customer = mapper.map(dto, Customer.class);
customerRepo.save(customer);
for (UserDTO ud : dto.getUser()){
if(!userRepo.existsById(ud.getEmail())){
UserDTO userDTO = new UserDTO(ud.getEmail(),ud.getPassword(),ud.getRole(),ud.getLastLogged());
User user = new User(userDTO.getEmail(), userDTO.getPassword(), userDTO.getRole(), userDTO.getLastLogged(), customer);
//User user = mapper.map(userDTO, User.class);
userRepo.save(user);
}else {
throw new RuntimeException("Email is already exist!");
}
}
}else{
throw new RuntimeException("Customer is already exist!");
}
}
I tried to send these Json value Using postman
{
"nic" : "55665v",
"name" : "anyname",
"address" : "no 20,56 text",
"contact" : "54673453",
"user": [{
"email":"text#gmail.com",
"password":"1234",
"role":"driver",
"lastLogged":"sunday"
}]
}
And each time I am calling my function I get
ids for this class must be manually assigned before calling save(): lk.EasyCarRental.backend.entity.Customer; nested exception is org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): lk.EasyCarRental.backend.entity.Customer
i do not want to auto generate id. I wont to manually input id
Not sure what the purpose of Customer customerNic = customerRepo.findCustomerByNic(dto.getNic()); is, but since you are not showing that, it's hard to say what's going on. Have you tried using just customer which you persisted prior to entering the loop instead?
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);
}
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);
}
}