java.lang.StackOverflowError: null [Spring Boot, Hibernate]

java.lang.StackOverflowError: null [Spring Boot, Hibernate]

我有两个 classes User.javaAddress.java 还有一个 它们之间的一对一双向映射

但是当我尝试使用 User class 获取地址时,我得到一个 "java.lang.WhosebugError: null"异常。

当我尝试从 Address class.

获取 User 时,同样的事情发生了

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";
    }
}

我正在使用 test() 函数将数据放入数据库,它工作正常。 database image

但是当我调用 fetch() 函数时出现以下错误

java.lang.WhosebugError: 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 

已更新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.WhosebugError:null
        System.out.println(user.getAddress());
        
        return "working";
    }
}

TLDR:您实际上并没有在任何地方保存任何东西,但它很容易修复。这是我的代码和解释:

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;
    }
}

DAO 与数据库没有连接,它的意图是首字母缩略词所代表的,只是为了传输数据,仅此而已。创建存储库时,您可以通过将对象保存在存储库中来将它们粘贴在那里。请注意,通过使用正确的泛型扩展 CrudRepository,您甚至不需要自己实现这些方法。 save 方法实际上保存了 POJO,而 returns 保存了版本,这就是为什么我这样做 user = userRepository.save(user),乍一看似乎违反直觉,但它只是帮助确保一切如你所愿预计。如果您随后想发送 UserDAO 对象作为响应,您可以使用从数据库返回的 user 对象创建它,可能类似于:

UserDAO dao = new UserDAO(userRepository.save(user));

请注意MainControllertest方法内部发生的事情。首先,我们创建 POJO User 对象并设置它的字段。然后我们必须将它保存到存储库中,它只有在您调用存储库的 save 方法后才会持久化。请注意,user 对象在使用 address.

更新后会再次保存

这是一种非常粗糙的做事方式,最好创建一个服务层并在其中使用 @Transactional 注释执行此操作,这意味着一切都会回滚,以防万一内部出现问题注释为 @Transactional.

的方法

另外,使用CascadeType.ALL可能不是你想要的,请参考这个answer

fetch 方法中,我确保 user 确实存在,但不能保证。为避免 500 错误,重要的是要有一个回退机制,以应对出现问题的情况。

最后一点,你不应该像那样存储原始密码,你至少应该使用加盐和胡椒的散列,或者使用许多可用的库之一来实现这样的功能(尽管它可以是弄脏代码本身非常有趣)。您还应该考虑当出现问题时您透露了多少信息,因为您不想泄露太多可能被用来对特定用户进行匿名化处理的信息,甚至不想了解有关您的代码和系统架构的更多信息。