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.
@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;
}
}
@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;
@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";
}
@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));
}
}
@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";
}
@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
. 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.
@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";
}
@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
Removed the OneToOne here as its not needed
@Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
Team findAllById(Long id);
long countAllByUser(final User user);
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.