3

In spring app:

User.java:

@Entity
@Data
@NoArgsConstructor
public class User {
    @Id @GeneratedValue
    private Long id;
    @Column(unique = true)
    private String username;
    private String about;
    @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @MapKey(name = "friendId")
    private Map<User, Friendship> friendships = new HashMap<>();
    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @MapKey(name = "title")
    private Map<String, Post> posts = new HashMap<>();

    public User(String username) {
        this.username = username;
    }
    public User addFriend(User friend){
        Friendship friendship = new Friendship();
        friendship.setOwner(this);
        friendship.setFriend(friend);

        friend.getFriendships().put(this, friendship);
        getFriendships().put(friend, friendship);
        return friend;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        return this.username.equals(user.getUsername());
    }

    @Override
    public int hashCode() {
        return this.username.hashCode();
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", about='" + about + '\'' +
                ", posts=" + posts +
                '}';
    }
}

Friendship.java:

@IdClass(Friendship.class)
@Entity
@Data
public class Friendship implements Serializable {
    @Id @Column(name = "owner_id")
    private Long ownerId;
    @Id @Column(name = "friend_id")
    private Long friendId;
    @ManyToOne @MapsId("owner_id")
    private User owner;
    @ManyToOne @MapsId("friend_id")
    private User friend;
    private String level;
}

DemoApplication:

@Bean
    public CommandLineRunner loadData(UserRepository userRepo){
        return new CommandLineRunner() {
            @Override
            public void run(String... args) throws Exception {
                User owner = new User("Barta");
                User martin = owner.addFriend(new User("Martin"));
                User milan = owner.addFriend(new User("Milan"));
                userRepo.save(owner);
            }
        };
    }

When run, the error is Cannot invoke "java.lang.Object.hashCode()" because "value" is null where I suppose the "value" is the username. But the username is set in the constructor (I can see it even in debugging), so why do I get NullPointerException?

PS:

The full error stack:

java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:794) ~[spring-boot-2.5.3.jar:2.5.3]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:775) ~[spring-boot-2.5.3.jar:2.5.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:345) ~[spring-boot-2.5.3.jar:2.5.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-2.5.3.jar:2.5.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332) ~[spring-boot-2.5.3.jar:2.5.3]
    at com.example.demo.DemoApplication.main(DemoApplication.java:16) ~[classes/:na]
Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.Object.hashCode()" because "value" is null
    at org.hibernate.type.descriptor.java.AbstractTypeDescriptor.extractHashCode(AbstractTypeDescriptor.java:78) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:200) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:205) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.type.EntityType.getHashCode(EntityType.java:383) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.type.ComponentType.getHashCode(ComponentType.java:249) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.engine.spi.EntityKey.generateHashCode(EntityKey.java:61) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.engine.spi.EntityKey.<init>(EntityKey.java:54) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.internal.AbstractSharedSessionContract.generateEntityKey(AbstractSharedSessionContract.java:536) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:172) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:135) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:185) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.internal.SessionImpl$$Lambda$1219/0x00000000a219d8f8.applyEventToListener(Unknown Source) ~[na:na]
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:110) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:744) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:712) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.engine.spi.CascadingActions$7.cascade(CascadingActions.java:298) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:499) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:423) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:532) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:463) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:426) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:153) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:459) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:293) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:193) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:135) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:185) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:55) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.internal.SessionImpl$$Lambda$1218/0x00000000a21505e0.accept(Unknown Source) ~[na:na]
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:99) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:720) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:706) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:311) ~[spring-orm-5.3.9.jar:5.3.9]
    at jdk.proxy2/jdk.proxy2.$Proxy98.persist(Unknown Source) ~[na:na]
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:597) ~[spring-data-jpa-2.5.3.jar:2.5.3]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289) ~[spring-data-commons-2.5.3.jar:2.5.3]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker$$Lambda$1217/0x00000000a214e200.invoke(Unknown Source) ~[na:na]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137) ~[spring-data-commons-2.5.3.jar:2.5.3]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121) ~[spring-data-commons-2.5.3.jar:2.5.3]
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:529) ~[spring-data-commons-2.5.3.jar:2.5.3]
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285) ~[spring-data-commons-2.5.3.jar:2.5.3]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:599) ~[spring-data-commons-2.5.3.jar:2.5.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.9.jar:5.3.9]
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:163) ~[spring-data-commons-2.5.3.jar:2.5.3]
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:138) ~[spring-data-commons-2.5.3.jar:2.5.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.9.jar:5.3.9]
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80) ~[spring-data-commons-2.5.3.jar:2.5.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.9.jar:5.3.9]
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.9.jar:5.3.9]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.9.jar:5.3.9]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.9.jar:5.3.9]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.9.jar:5.3.9]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137) ~[spring-tx-5.3.9.jar:5.3.9]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.9.jar:5.3.9]
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174) ~[spring-data-jpa-2.5.3.jar:2.5.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.9.jar:5.3.9]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-5.3.9.jar:5.3.9]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.9.jar:5.3.9]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.9.jar:5.3.9]
    at jdk.proxy2/jdk.proxy2.$Proxy103.save(Unknown Source) ~[na:na]
    at com.example.demo.DemoApplication$1.run(DemoApplication.java:27) ~[classes/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:791) ~[spring-boot-2.5.3.jar:2.5.3]
    ... 5 common frames omitted

2021-09-13 15:55:23.677  INFO 3218 --- [           main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2021-09-13 15:55:23.682  INFO 3218 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2021-09-13 15:55:23.694  INFO 3218 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Process finished with exit code 

PSS:

the intellij is marking the

@MapKey(name = "friendId")

with error:

'java.lang.Long' cannot be assigned to 'com.example.demo.model.User'

As I am trying to understand -> the id is assign null when creating which is expecting. So the only way the id will be assign a value (an primary key) is in time of persisting (otherwise I have no idea, when will JPA assign values to primary keys, when @GenerateValue is used)

milanHrabos
  • 2,010
  • 3
  • 11
  • 45
  • Don't use `@Data` for entities, that is a bad idea. See https://deinum.biz/2019-02-13-Lombok-Data-Ojects-Arent-Entities/ . How to write a proper hashCode and equals method is explained here https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/. Finally when JPA creates an entity it will use the no-args constructor, so at that point there is no `username` (yet) and your hashCode and equals method will fail when populating the map of friends. – M. Deinum Sep 13 '21 at 06:15
  • @M.Deinum first 1) the hashCode is using the username which it gets from constructor, that has nothing to do how JPA is creating the entity for DB (hashCode is for JVM not DB). 2) If it was your case, then how would you even implement hashCode when it uses Noargs and thus there is no field you can use within hashCode() ?? 3) the `username` is business key (naturalId) which as your link suggest is the best option – milanHrabos Sep 13 '21 at 07:43
  • It isn't the natural id as it hasn't been marked as such. No the username will not be set through the constructor when JPA is creating it when reading. Again I pointed you to all the information necessary (which you apparently didn't even bother to read). It is still a bad idea to combine `@Data` and entities and your `Friendship` doesn't have an equals/hashCode and you are stuffing it in a map. However we have no way of knowing **what** it throwing the exception as you only include a tiny snippet of a long stack trace. – M. Deinum Sep 13 '21 at 08:25
  • @milanHrabos The best approach would be to remove the `@NoArgsConstructor` and check whether or not the application runs. It should prove what is correct approach here. – yoni Sep 13 '21 at 12:24
  • @yoni still the same nullPointerExceptionError – milanHrabos Sep 13 '21 at 13:52
  • @M.Deinum you are right, it is not marked as NaturalId, yet it is Unique column. The link you send, there is using `entityManager`, I am using `JpaRepository` as you can see from the `userRepository.save()`. And about the error stack see the edits I posted the full stack. I doubt there will be some other info that could be helpful – milanHrabos Sep 13 '21 at 13:54
  • save or entity manager doesn't matter, in the end it is all `EntityManager.persist` or `EntityManager.merge`. The fact that you use Spring Data JPA doesn't mean the regular JPA rules don't apply anymore. – M. Deinum Sep 13 '21 at 14:03
  • Your model is quite complicated indeed... I think Hibernate is surely complaining when cascading the different relationships between `User`s through `Friendship`. I would remove this line in `addFriend`: `friend.getFriendships().put(this, friendship);`. These two cascades... If you wanna build a relationship like if you are my friend then I am a friend of you I think it should be preferable create a business method that performs two `addFriend`s operations, once per user in the friendship. In fact, you will create two `Friendship`s records. – jccampanero Sep 16 '21 at 22:00
  • @jccampanero still the same `nullPointerException` – milanHrabos Sep 16 '21 at 22:45
  • See https://stackoverflow.com/questions/6744451/hibernate-nm-extracthashcode-throws-nullpointerexception – tgdavies Sep 16 '21 at 23:02
  • @tgdavies yeah, that's the point. In the link you provided, they are using `@Embeddable` and using that as composed primary key. I used it as well, and it worked. But why cannot I use `@IdClass` and `@MapsId` as well? What is wrong using `@IdClass`? – milanHrabos Sep 17 '21 at 08:47
  • Sorry to hear that it didn't work @milanHrabos. The idea behind my suggestion is that there is something strange in the relationships through `Friendship`. In addition, maybe related with the cited question by tgdavies, in `DemoApplication` you are saving the user `Barta`. According to your code and the provided `cascade` information, the field `friendships` will be persisted as well. But please, be aware that `Friendship` will no `cascade` any further information and in the test case the users `Martin` and `Milan` has not been persisted yet, and it will be not persisted unless explicitly done – jccampanero Sep 17 '21 at 12:44
  • And you need the generated user id for both `owner` but also `friend` in order to create the actual `Friendship` record. It seems that similar issues had been reported to Hibernate: [HHH-4469](https://hibernate.atlassian.net/browse/HHH-4469), [HHH-2326](https://hibernate.atlassian.net/browse/HHH-2326), [HHH-1478](https://hibernate.atlassian.net/browse/HHH-1478). – jccampanero Sep 17 '21 at 13:19
  • Looking at the code snippet above, @IdClass(Friendship.class) is marked on Friendship.class itself. Is it expected ? – S B Sep 21 '21 at 17:05

3 Answers3

3

Most of the time I would use the id for equals and hashCode in an @Entity

 @Override
public boolean equals(Object o)
{
    if (this == o)
    {
        return true;
    }
    if (o == null || getClass() != o.getClass())
    {
        return false;
    }
    User user = (User) o;
    return Objects.equals(id, disclaimer.id);
}

@Override
public int hashCode()
{
    return Objects.hash(id);
}

If you really have reasons to have the name as key, I would suggest you to read something about @NaturalId and make the hashCode Method null save

@Override
public int hashCode()
{
    return Objects.hash(name);
}
Andreas Radauer
  • 1,083
  • 7
  • 18
  • The id is surrogate key, meaning it could change from through persistance changes. So before the entity is in persistent state, the id is `null` and after that it will change (will be Generated and thus will change the `hashCode`) you should use bussiness key which does not change between persistence state. And even if I used the primary key. the initial value is `null` and primary key cannot be null – milanHrabos Sep 16 '21 at 10:15
0

What happens if you try the following code?

User barta = new User("Barta");
User martin = new User("Martin");
User milan = new User("Milan");

userRepo.save(barta);
userRepo.save(martin);
userRepo.save(milan);

User martin = barta.addFriend(martin);
User milan = owner.addFriend(martin);

userRepo.save(martin);
userRepo.save(milan);

Can you tell which line exception is thrown eventually?

ValerioMC
  • 2,926
  • 13
  • 24
0

Looking at the stack trace it seems that the exception raises when processing on cascade. So probably it fails when getting Friendship hashcode instead of User. Friedship hashcode (generated by Project Lombok) is made with hashcode of all properties. One of these properties is the "owner" User. So maybe this cycle is the culprit.

I'd try to persist "martin" and "milan" before saving "owner".

sinuhepop
  • 20,010
  • 17
  • 72
  • 107