【问题标题】:Spring Data findOne() NullPointerExceptionSpring Data findOne() NullPointerException
【发布时间】:2017-09-10 11:57:43
【问题描述】:

我想我错过了一些核心概念,因为我遇到了几个问题,但让我们从这个开始:当 UserSubscription 被持久化在数据库中时,我尝试使用 findOne(id) 获取它,我得到NullPointerException。我试图深入调试生成的代码,似乎出于某种原因,hashCode()Subscription 对象被调用,也由于不清楚的原因,它只有一个 id 集,所有其他属性都是 null,但因为它们(可能)通过调用自己的hashCode() 参与hashCode() 方法,我得到了这个异常。

所以基本上我想要的是用户成为许多社区的一部分,在每个社区中,他都可以创建对其内容的订阅。当我第一次调用SubscriptionController 时,一切正常,它创建了UserSubscriptionCommunity,我可以在数据库中看到它们,一切都很好。但是当我在UserSerivce 内部调用UserRepository.findOne()(即CrudRepository)时 - 我得到了异常。

我已经尝试解决这个问题两周了,但没有运气,所以我真的希望有人能花一些时间帮助我解决这个问题。 以下是类:

用户:

@Entity
@Data
@NoArgsConstructor
public class User {
    @Column(nullable = false)
    @Id
    private Integer id;

    @OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JsonIgnore
    Set<Subscription> subscriptions;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(
            joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
            inverseJoinColumns = {@JoinColumn(name = "payment_id", referencedColumnName = "id", unique = true)}
    )
    @JsonIgnore
    Set<Payment> payments;

    public User(Integer userId) {
        this.id = userId;
    }
}

订阅:

@Entity
@Data
@NoArgsConstructor
public class Subscription {
    @Column
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @JsonIgnore
    private Integer id;

    @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH})
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH})
    @JoinColumn(name = "community_id", nullable = false)
    private Community community;

    @Column(nullable = false)
    private Boolean isActive;

    @Column(nullable = false)
    private Date endDate;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(
            joinColumns = {@JoinColumn(name = "subscription_id", referencedColumnName = "id")},
            inverseJoinColumns = {@JoinColumn(name = "payment_id", referencedColumnName = "id", unique = true)}
    )
    private Set<Payment> payments;

    public Subscription(User user, Community community, Boolean isActive) {
        this.user = user;
        this.community = community;
        this.isActive = isActive;
        this.endDate = new Date();
    }
}

社区:

@Data
@Entity
@NoArgsConstructor
public class Community {
    @Column(nullable = false)
    @Id
    private Integer id;

    @OneToMany(mappedBy = "community", fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.REFRESH})
    @JsonIgnore
    private Set<Subscription> subscriptions;

    public Community(Integer communityId) {
        this.id = communityId;
    }
}

我也为他们每个人提供服务:

用户服务:

@Service
public class UserService implements IService<User> {
    @Autowired
    private UserRepository userRepository;

    @Transactional
    public User get(@NotNull Integer userId) {
        User user = userRepository.findOne(userId);
        if (user == null)
            return userRepository.save(new User(userId));
        return user;
    }

    @Override
    public User save(@Valid User user) {
        return userRepository.save(user);
    }
}

订阅服务:

@Service
public class SubscriptionService implements IService<Subscription> {
    @Autowired
    SubscriptionRepository subscriptionRepository;
    @Autowired
    PaymentRepository paymentRepository;

    @Override
    public Subscription get(@NotNull Integer id) {
        return subscriptionRepository.findOne(id);
    }

    public Subscription getByUserAndCommunity(@Valid User user, @Valid Community community) {
        Subscription subscription = subscriptionRepository.findByUserAndCommunity(user, community);
        if (subscription != null)
            return subscription;
        subscription = new Subscription(user, community, false);
        return subscriptionRepository.save(subscription);
    }

    @Transactional
    public Subscription activate(@Valid Subscription subscription, @Valid Payment payment, @Future Date endDate) {
        paymentRepository.save(payment);
        Set<Payment> payments = subscription.getPayments();
        if (payments == null)
            payments = new HashSet<>();
        payments.add(payment);
        subscription.setEndDate(endDate);
        subscription.setIsActive(true);
        return subscriptionRepository.save(subscription);
    }

    @Override
    public Subscription save(@Valid Subscription e) {
        return subscriptionRepository.save(e);
    }
}

和社区服务:

@Service
public class CommunityService implements IService<Community> {
    @Autowired
    private CommunityRepository communityRepository;

    @Override
    @Transactional
    public Community get(@NotNull Integer id) {
        Community community = communityRepository.findOne(id);
        if (community == null)
            return communityRepository.save(new Community(id));
        return community;
    }

    @Override
    public Community save(@Valid Community community) {
        return communityRepository.save(community);
    }
}

控制器:

@RestController
public class SubscriptionController {
    @Autowired
    private SubscriptionService subscriptionService;
    @Autowired
    private CommunityService communityService;
    @Autowired
    private PaymentService paymentService;

    @PostMapping("/subscribe")
    public ResponseEntity<Subscription> subscribe(@RequestParam("communityId") Integer communityId, @RequestBody @Valid Payment payment) {
        if(!paymentService.checkPayment(payment))
            return ResponseEntity
                    .status(HttpStatus.BAD_REQUEST)
                    .body(null);

        VkAuthentication vkAuthentication = (VkAuthentication) SecurityContextHolder.getContext().getAuthentication();
        User user = vkAuthentication.getUser();

        Community community = communityService.get(communityId);
        Subscription subscription = subscriptionService.getByUserAndCommunity(user, community);

        Calendar calendar = Calendar.getInstance();
        Date newEndDate = DateUtils.addDays(new Date(), calendar.getActualMaximum(Calendar.DAY_OF_MONTH));

        subscription = subscriptionService.activate(subscription, payment, newEndDate);
        return ResponseEntity
                .status(HttpStatus.OK)
                .body(subscription);
    }
}

这是一些堆栈跟踪:

java.lang.NullPointerException: null
    at org.hibernate.engine.internal.StatefulPersistenceContext.getLoadedCollectionOwnerOrNull(StatefulPersistenceContext.java:786) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.event.spi.AbstractCollectionEvent.getLoadedOwnerOrNull(AbstractCollectionEvent.java:58) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.event.spi.InitializeCollectionEvent.<init>(InitializeCollectionEvent.java:22) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:1989) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.collection.internal.AbstractPersistentCollection$4.doWork(AbstractPersistentCollection.java:570) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:252) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:566) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:135) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:430) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at zhiyest.subscriptionsbackend.domain.User.hashCode(User.java:14) ~[classes/:na]
    at zhiyest.subscriptionsbackend.domain.Subscription.hashCode(Subscription.java:15) ~[classes/:na]
    at java.util.HashMap.hash(HashMap.java:338) ~[na:1.8.0_111]
    at java.util.HashMap.put(HashMap.java:611) ~[na:1.8.0_111]
    at java.util.HashSet.add(HashSet.java:219) ~[na:1.8.0_111]
    at java.util.AbstractCollection.addAll(AbstractCollection.java:344) ~[na:1.8.0_111]
    at org.hibernate.collection.internal.PersistentSet.endRead(PersistentSet.java:327) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection(CollectionLoadContext.java:234) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:221) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:194) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl.endLoading(CollectionReferenceInitializerImpl.java:154) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishLoadingCollections(AbstractRowReader.java:249) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at ...

我什至不明白为什么它调用Subscription.hashCode() 而它是findOne() for User...

更新:

at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57) ~[spring-data-commons-1.13.4.RELEASE.jar:na]
    ... 
    at zhiyest.subscriptionsbackend.logging.Logger.logAround(Logger.java:29) ~[classes/:na]
    ...
    at zhiyest.subscriptionsbackend.services.UserService$$EnhancerBySpringCGLIB$$6e00bac4.get(<generated>) ~[classes/:na]
    at zhiyest.subscriptionsbackend.security.VkAuthenticationProvider.authenticate(VkAuthenticationProvider.java:23) ~[classes/:na]
    at zhiyest.subscriptionsbackend.security.VkAuthenticationProvider$$FastClassBySpringCGLIB$$24f3d662.invoke(<generated>) ~[classes/:na]
    ...
    at zhiyest.subscriptionsbackend.security.VkAuthenticationProvider$$EnhancerBySpringCGLIB$$4d8d8001.authenticate(<generated>) ~[classes/:na]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174) ~[spring-security-core-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:199) ~[spring-security-core-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.authenticateIfRequired(AbstractSecurityInterceptor.java:354) ~[spring-security-core-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:229) ~[spring-security-core-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    ...

【问题讨论】:

  • 你能粘贴完整的异常堆栈跟踪吗?
  • @MadhusudanaReddySunnapu 恐怕不行,这里有 30k 个字符的限制
  • Okie.. 我想查看异常堆栈跟踪中包含来自服务/控制器的方法的部分
  • @MadhusudanaReddySunnapu 添加。它只是在身份验证提供程序内部调用 UserService.get() 以实现 Spring 安全性,因此归结为 UserRepository.findOne()
  • @LeonidBor 用户有很多订阅,他们被急切地获取。看起来 subscription.hascode() 包括 user.hashcode() 并且某处有混乱

标签: java spring-boot spring-data lombok


【解决方案1】:

我猜这个问题是@Data

这个lombok注解是递归依赖的原因(toString()hashcode())。 尝试使用@Getter@Setter 而不是@Data

我希望它会有所帮助。

【讨论】:

  • 你不需要走这么远 - 只需从 equals/hashcode 中删除嵌套的 Set。我将对此发布单独的答案。
【解决方案2】:

这似乎是某个版本的 Hibernate 中的一个错误(请参阅下面的第一篇文章)。如果您有一组嵌套的其他 Hibernate 实体,则在其 hashCode() 方法中访问它们似乎有问题。我通过调试确认了这一点。

您可以手动生成哈希/代码等于并删除其他实体。或者你可以在你的龙目岛做这样的事情,下面第二篇文章中的评论建议:

@EqualsAndHashCode(exclude={"subscriptions"})

我说“某事”是因为我没有仔细阅读您的对象图以了解您应该排除哪一个。但这是一个简单的解决方案,而且一般来说,将对象的附属内容作为其逻辑标识的一部分是没有意义的。

Hibernate 4.2.20 Object HashCode Method Causing NullPointerException

How do you retrieve nested Sets?

【讨论】:

  • 我猜你错过了@ToString的排除
  • @AndrewNepogoda 我没有看到toString() 的问题,所以我不需要修复它。我不确定 OP 是否看到了它。我相信你是提出这个问题的人,我认为这与 Hibernate 问题无关 - 但是,如果你遇到递归问题,你也可以对其进行注释。
猜你喜欢
  • 1970-01-01
  • 2018-02-04
  • 1970-01-01
  • 2014-09-22
  • 1970-01-01
  • 2018-04-07
  • 2018-08-25
  • 2018-02-26
  • 1970-01-01
相关资源
最近更新 更多