【问题标题】:Hibernate: Cannot fetch data back to Map<>休眠:无法将数据取回 Map<>
【发布时间】:2021-09-09 12:34:17
【问题描述】:

拥有这个实体:

User.java

@Entity
@Data
@NoArgsConstructor
public class User {
    @Id @GeneratedValue
    private int id;
    private String username;
    private String about;
    @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
    private Map<User, Friendship> friendships = new HashMap<>();
    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
    private Collection<Post> posts = new ArrayList<>();

    public User(String username) {
        this.username = username;
    }

    public void addFriend(User friend){
        Friendship friendship = new Friendship();
        friendship.setOwner(this);
        friendship.setFriend(friend);
        this.friendships.put(friend, friendship);
    }

    public void addPost(Post post){
        post.setAuthor(this);
        this.posts.add(post);
    }
}

Friendship.java

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Friendship {
    @EmbeddedId
    private FriendshipId key = new FriendshipId();
    private String level;
    @ManyToOne
    @MapsId("ownerId")
    private User owner;
    @ManyToOne
    @MapsId("friendId")
    private User friend;
}

FriendshipId.java

@Embeddable
public class FriendshipId implements Serializable {
    private int ownerId;
    private int friendId;
}

UserRepository.java

public interface UserRepository extends JpaRepository<User, Integer> {
    public User findByUsername(String username);
}

最后DemoApplication.java

@Bean
    public CommandLineRunner dataLoader(UserRepository userRepo, FriendshipRepository friendshipRepo){
        return new CommandLineRunner() {
            @Override
            public void run(String... args) throws Exception {
                User f1 = new User("friend1");
                User f2 = new User("friend2");
                User u1 = new User("user1");

                u1.addFriend(f1);
                u1.addFriend(f2);
                userRepo.save(u1);

                User fetchedUser = userRepo.findByUsername("user1");
            System.out.println(fetchedUser);
            System.out.println(fetchedUser.getFriendships().get(f1));

            }
        };
    }

userRepo.save(u1)操作后的表格如下:

mysql> select * from user;
+----+-------+----------+
| id | about | username |
+----+-------+----------+
|  1 | NULL  | user1    |
|  2 | NULL  | friend1  |
|  3 | NULL  | friend2  |
+----+-------+----------+

select * from friendship;
+-------+-----------+----------+-----------------+
| level | friend_id | owner_id | friendships_key |
+-------+-----------+----------+-----------------+
| NULL  |         2 |        1 |               2 |
| NULL  |         3 |        1 |               3 |
+-------+-----------+----------+-----------------+

如您所见,所有朋友都已保存。然而这句话:

        System.out.println(fetchedUser.getFriendships().get(f1));

返回null。即使fetchedUser 获取了朋友地图:

        System.out.println(fetchedUser);

打印:

User(id=1, username=user1, about=null, friendships={User(id=2, username=friend1, about=null, friendships={}, posts=[])=com.example.demo.model.Friendship@152581e8, User(id=3, username=friend2, about=null, friendships={}, posts=[])=com.example.demo.model.Friendship@58a5d38}, posts=[])

那么,当地图friendships 被完全提取(所有朋友都被提取,从上面的语句中可以看出)时,为什么无法提取朋友f1(更确切地说是null)?

PS:

我已经删除了@Data lombok 注释(只是添加了@Getter@Setter 和@NoArgsConstrutor`)并自己​​覆盖了equalsAndHashCode:

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        return id == user.id && Objects.equals(username, user.username) && Objects.equals(about, user.about) && Objects.equals(friendships, user.friendships) && Objects.equals(posts, user.posts);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, username, about, friendships, posts);
    }

或者换句话说,equals() 方法使用了User 类的所有字段。

【问题讨论】:

  • 我认为您缺少 EqualsAndHashcode 实现。
  • @SKumar 用户有 @Data lombok 注释,它应该覆盖 EqualAndHascode。
  • 好的,但它是否知道在User 类的所有字段中,它只需要在username 字段上进行验证? id 也已填充。您能否确认,如果您登录 f1,它已填充了 id。我怀疑它没有。
  • @SKumar 我不明白你的意思,但如果你看一下编辑,我自己已经覆盖了 equals 和 hashCode(IDE 确实做到了)
  • @SKumar 被覆盖的equals() 现在正在使用所有字段

标签: java spring hibernate crud


【解决方案1】:

如您所见,所有朋友都已保存。然而这句话:

    System.out.println(fetchedUser.getFriendships().get(f1)); returns null. 

即使 fetchedUser 有朋友地图 获取:

    System.out.println(fetchedUser);

打印:

 User(id=1, username=user1, about=null, friendships={User(id=2, username=friend1, about=null, friendships={}, posts=[])=com.example.demo.model.Friendship@152581e8, User(id=3, username=friend2, about=null, friendships={}, posts=[])=com.example.demo.model.Friendship@58a5d38}, posts=[])

问题是当f1 User 添加到friendships HashMap 时,主键id 不存在。它稍后会在某个时候由 Hibernate 更新。这会改变 HashCode 值!!!

hashcode 键的值在添加到Map 后不应更改。这导致了这个问题。模拟问题的简单测试代码 - https://www.jdoodle.com/a/3Bg3

import lombok.*;
import java.util.*;

public class MyClass {
    public static void main(String args[]) {
      Map<User, String> friendships = new HashMap<>();
        User f1 = new User();
        f1.setUsername("friend1");
        
        User f2 = new User();
        f2.setUsername("friend2");
        friendships.put(f1, "I am changed. Can't find me");
        friendships.put(f2, "Nothing changed. So, you found me");
        
        System.out.println(f1.hashCode()); // -600090900
        f1.setId(1); // Some id gets assigned by hibernate. Breaking the hashcode
        System.out.println(f1.hashCode()); // -600090841 (this changed !!!)

        System.out.println(friendships); // prints f1, f2 both
        System.out.println(friendships.get(f1)); // prints null
        System.out.println(friendships.get(f2));
    }
}

// @Data
@Getter
@Setter
@EqualsAndHashCode
@ToString
class User
{
    private int id;
    private String username;
}

解决方案

在将用户添加到地图后,不应更改 hashcode 值。我认为有几个选项可以尝试解决这个问题 -

  1. 在将好友放入friendship 地图之前,将其保留在数据库中。所以这个 id 已经分配好了。
  2. 根本不要覆盖equalshashcode。使用默认值。基于对象身份。
  3. 使用固定的哈希码。例如,如果username在分配后从未改变,则该字段可用于生成hashcode值。

【讨论】:

  • 嗯,第二点根本不生成哈希和等于...可能不是理想的方法,但我真的需要覆盖这些方法吗?如果我没有在代码中明确使用 equals,那么我真的需要它吗?我知道Map&lt;&gt; 正在使用它,但我没有,所以我不需要覆盖它,对吧?
  • @milanHrabos 看起来您不需要像 stackoverflow.com/q/1638723/11244881 解释的那样重写这些方法。
  • hashCode被破坏的原因不是id的变化,而是因为收藏。请看这里 -> stackoverflow.com/questions/69142972/… 。我只使用id 制作了equalsAndHasCode,但它仍然有效(但它不应该-> 因为正如你所说,idnull,然后在持久化时由休眠分配)。这怎么可能?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-24
  • 2014-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-04
相关资源
最近更新 更多