I think I'm missing some core concept, because I encounter several problems, but let's start with this one: when a User
with a Subscription
is persisted in database and I try to get it using findOne(id)
, I get NullPointerException
. I tried to debug deep inside generated code and it appears that for some reason hashCode()
of Subscription
object is called, which also for unclear reason has only an id
set and all other properties are null
, but because they (probably) take part in the hashCode()
method by calling their own hashCode()
, I get this exception.
So basically what I want is user be a part of many communities, in each of them he can create a subscription to their content. When I first call to SubscriptionController
, everything goes fine and it creates User
, Subscription
and Community
, I can see them in database, all good. But then when I call UserRepository.findOne()
, which is CrudRepository
, inside UserSerivce
- I get the exception.
I've been trying to figure this out for two weeks and no luck, so I really hope someone can spend some time helping me with this. Below are classes:
User:
@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;
}
}
Subscription:
@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();
}
}
Community:
@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;
}
}
I also have services for each of them:
UserService:
@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);
}
}
SubscriptionService:
@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);
}
}
And CommunityService:
@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);
}
}
Controller:
@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);
}
}
And here's some stack trace:
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 ...
I don't even understand why does it call Subscription.hashCode()
when it's findOne()
for User
...
upd:
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.ProviderMa