简体   繁体   中英

@OneToOne mapping isnt working in my Java Spring project

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:

  1. @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.
  2. 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 .
  3. 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);

}

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM