【问题标题】:Java Model Hibernate Mapping IssueJava 模型休眠映射问题
【发布时间】:2021-01-19 19:16:00
【问题描述】:

注意:我使用 Spring Data Jpa 进行持久化。

问题:

我有两个模型:UserBadge

我有一个用户拥有的徽章列表作为 User 类中的数据成员。 我还有用户作为 徽章 类中的数据成员(即徽章的创建者)

我想在用户和徽章数据成员列表之间建立关系。 关系是 OneToMany 类型(即一个用户将拥有许多徽章),反之亦然。

我希望它以这种方式工作, 在代码中,

当我将颁发者(又名用户)设置为特定用户对象的徽章对象保存时,无需将其(徽章)添加到用户拥有的徽章列表中。

我已尝试创建关系,但它在 REST API 响应中返回用户拥有的徽章的空列表。

徽章模型

import javax.persistence.*;

@Entity
@Table(name = "badges")
public class Badge {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "badge_id")
    private int mId;
    @Column(name = "badge_name" , nullable = false , unique = true)
    private String mName;
    @Column(name = "badge_description")
    private String mDescription;
    @Lob
    @Column(name = "badge_logo" , nullable = false)
    private String mLogo;
    @ManyToOne
    @JoinColumn(name = "issuer_id")
    private User mIssuer;
}

用户模型

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "users")
public class User {
    @Id@GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "user_id")
    private long mId;
    @Column(name = "username" , nullable = false , unique = true)
    private String mUserName;
    @Column(name = "fullname",nullable = false)
    private String mFullName;
    @Column(name = "salt")
    private String mSalt;
    @OneToMany(mappedBy = "mIssuer",cascade = CascadeType.ALL)
    private List<Badge> mOwnedBadges;
    @OneToMany
    @JoinColumn(name = "received_badges_id")
    private List<Badge> mReceivedBadges;
}

CommandLineRunner

import com.badging.spinnerbadger.SpinnerBadger.Models.Badge;
import com.badging.spinnerbadger.SpinnerBadger.Models.User;
import com.badging.spinnerbadger.SpinnerBadger.Services.Intefaces.BadgeSerivce;
import com.badging.spinnerbadger.SpinnerBadger.Services.Intefaces.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
public class StartupExecutor implements CommandLineRunner {
    @Autowired
    private BadgeSerivce mBadgeSerivce;
    @Autowired
    private UserService mUserService;

    @Override
    public void run(String... args) throws Exception {
        //TODO:: issuer cannot issue badge to itself
        final User user1 = new User();
        user1.setFullName("User1 FullName");
        user1.setSalt("salt1");
        user1.setUserName("User1 UserName");
        mUserService.save(user1);

        final User user2 = new User();
        user2.setFullName("User2 FullName");
        user2.setSalt("salt2");
        user2.setUserName("User2 UserName");
        mUserService.save(user2);


        Badge badge1 = new Badge();
        badge1.setDescription("Desc1");
        badge1.setLogo("Logo1");
        badge1.setName("Badge1");
        badge1.setIssuer(user1);
        mBadgeSerivce.save(badge1);

        Badge badge2 = new Badge();
        badge2.setDescription("Desc2");
        badge2.setLogo("Logo2");
        badge2.setName("Badge2");
        badge2.setIssuer(user2);
        mBadgeSerivce.save(badge2);

        Badge badge3 = new Badge();
        badge3.setDescription("Desc3");
        badge3.setLogo("Logo3");
        badge3.setName("Badge3");
        badge3.setIssuer(user1);
        mBadgeSerivce.save(badge3);


        user1.setReceivedBadges(Arrays.asList(badge2));

        user2.setReceivedBadges(Arrays.asList(badge1,badge3));
    }
}

注意:它也不会保存用户收到的徽章列表,如果你也能弄清楚,那我真的很感激你。

徽章回购

import com.badging.spinnerbadger.SpinnerBadger.Models.Badge;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BadgeRepo extends PagingAndSortingRepository<Badge,Long> {
}

用户回购

import com.badging.spinnerbadger.SpinnerBadger.Models.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepo extends JpaRepository<User,Long> {
}

BadgeServiceImpl

package com.badging.spinnerbadger.SpinnerBadger.Services.Implentations;

import com.badging.spinnerbadger.SpinnerBadger.Repository.BadgeRepo;
import com.badging.spinnerbadger.SpinnerBadger.Models.Badge;
import com.badging.spinnerbadger.SpinnerBadger.Services.Intefaces.BadgeSerivce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class BadgeServiceImpl implements BadgeSerivce {
    @Autowired
    private BadgeRepo mBadgeRepo;
    @Override
    public List<Badge> getAllBadges(int pageNumber , int sizeOfPage) {
        if (sizeOfPage > 20) {
            sizeOfPage = 20;
        }
        final Page<Badge> allPages = mBadgeRepo.findAll(PageRequest.of(pageNumber,
                sizeOfPage));
        if (allPages.getTotalElements() > 0) {
            return allPages.toList();
        } else{
            return new ArrayList<Badge>();
        }
    }

    @Override
    public void save(Badge badge) {
        mBadgeRepo.save(badge);
    }
}

UserServiceImpl

import com.badging.spinnerbadger.SpinnerBadger.Models.Badge;
import com.badging.spinnerbadger.SpinnerBadger.Models.User;
import com.badging.spinnerbadger.SpinnerBadger.Repository.UserRepo;
import com.badging.spinnerbadger.SpinnerBadger.Services.Intefaces.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserRepo mUserRepo;
    @Override
    public void save(User user) {
        mUserRepo.save(user);
    }

    @Override
    public List<Badge> getUsersReceivedBadgeList(long userId) {
        final Optional<User> byId = mUserRepo.findById(userId);
        return byId.orElse(new User()).getReceivedBadges();
    }

    @Override
    public List<Badge> getUserOwnedBadgeList(long userId) {
        final Optional<User> byId = mUserRepo.findById(userId);
        return byId.orElse(new User()).getReceivedBadges();
    }
}

由 Hibernate 生成的 SQL -> 用户模型第 1 名,徽章模型第 2 名

Hibernate: insert into users (fullname, salt, username, user_id) values (?, ?, ?, ?)

Hibernate: insert into badges (badge_description, issuer_id, badge_logo, badge_name, badge_id) values (?, ?, ?, ?, ?)

【问题讨论】:

  • 你在commit之后刷新你的更改吗?
  • 我觉得缺少一些有助于回答这个问题的东西。首先,不清楚 BadgeService 和 UserServiced 是如何实现的。在我的测试应用程序中,我为它们创建了扩展 JpaRepository 的接口。其次,我不遵循 mReceivedBadges 的映射方式。看着它,我觉得应该有另一个表和实体。在我的示例应用程序中。我无法让这部分按原样运行并将其排除在外。通过这两项更改,我能够在此函数中创建数据,然后在 REST 端点中检索具有自有徽章的用户。
  • @Vishrant 不,我没有刷新甚至提交,我正在使用 JpaRepository 的保存方法()来保存实体。
  • @darrendanvers BadgeService 和 UserService 分别抽象出 BadgeRepo(inteface) 和 UserRepo(inteface) 的操作。两个 Repo 接口都扩展了 JpaRepository。 我已将代码添加到帖子中,请参考那里
  • @darrendanvers 对于第二点,我也知道关系存在一些问题,我想我需要添加两个单独的实体,一个应该是 DisplayableBadgeRecievableBadge ,就像前者必须有拥有该对象的用户对象,后者必须有一个用户对象,他是徽章的接收者,并且必须有一个所有者(也就是创建它的人)用户对象。我想对了吗? ,尽管我的首要任务是首先获取所有者列表。

标签: spring-boot hibernate spring-data-jpa hibernate-mapping


【解决方案1】:

我在这里看到了一些可能出错的地方。

  1. 您似乎没有指定交易。将@Transactional 添加到那些应该参与事务的bean 或方法中。至少应该包括修改(最终)数据库的所有内容,即修改托管实体的任何语句,包括从数据库加载它的语句和save 语句。我预计这是您所看到问题的实际原因。

  2. 您似乎没有同步双向关系双方的代码。所以,当你打电话 badge1.setIssuer(user1), user1 没有更新,所以如果你打电话给user1.getOwnedBadges() 它仍然会返回未更改的(空)值。

    我怀疑在这种情况下这是一个问题,但它会导致 在单个交易中看起来不同的关系, 取决于你从哪一边看。和变化 非拥有方(在您的情况下为User)不会被持久化。所以这应该是固定的 无论如何。另见https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/

  3. 保存实体时,应使用save 方法返回的实例,而不是作为参数传递给save 的实例。通常它们是相同的,但是当它们不修改传递给 save 的那个时,可能不会导致状态持久化到数据库中。

如果这些问题得到解决并且问题仍然存在,我建议您通过以下方式收集有关正在发生的事情的更多信息:

  1. 激活logging of SQL statements including parameters 以查看实际保留的内容(以及何时保留)。

  2. 创建一个 JUnit 测试来测试您的服务。这使得实际执行的内容更加清晰,并允许创建变体进行比较。

【讨论】:

  • 首先,我感谢您为回答我的问题所付出的时间和精力。现在我将首先讨论第三点,JpaRepository 的保存方法不返回保存的实体,因此它是不可能的(尽管我可以创建一个自定义方法来保存实体并通过一些唯一标识符查询它)。现在我将谈谈你的第二点,我无法理解你在谈论哪种类型的代码同步,我已经很好地定义了 jpa 注释(看过很少的教程,他们做了同样的事情并且他们的映射工作)并且不需要同步。
  • save 返回一个实例。继承自CrudRepositorydocs.spring.io/spring-data/commons/docs/current/api/org/…
  • 现在我要谈谈你的第一点,你的意思是我应该在服务实现类中添加@Transactional来保存方法,对吗? 我添加了由 hibernate 生成的 SQL,用于将实体插入数据库,在我的帖子末尾参考那里。
  • 我更新了答案以澄清关于同步的观点。
  • @Transactional 应该添加到服务实现中的save 方法中,而且(在这种情况下更重要的是添加到StartupExecutor.run
猜你喜欢
  • 1970-01-01
  • 2018-01-29
  • 2011-01-31
  • 1970-01-01
  • 2014-05-12
  • 2021-01-13
  • 1970-01-01
  • 1970-01-01
  • 2014-03-12
相关资源
最近更新 更多