【问题标题】:How to fetch associated entities with Hibernate如何使用 Hibernate 获取关联实体
【发布时间】:2015-09-27 10:29:13
【问题描述】:

我正在使用 Spring rest 和 Hibernate 开发一个应用程序,我想从数据库中获取嵌套记录,例如我正在为 User 获取 Profession,现在我想获取与 Profession 关联的 Users我以前取过。

这是我的道课

@SuppressWarnings({ "unchecked", "rawtypes" })
public List<Profession> getProfessionById(long id) throws Exception {
    session = sessionFactory.openSession();
    Criteria cr = session.createCriteria(Profession.class);
    cr.add(Restrictions.eq("uid", id));
    List results = cr.list();
    tx = session.getTransaction();
    session.beginTransaction();
    tx.commit();
    return results;
}

【问题讨论】:

  • 条件 cr = session.createCriteria(User.class); cr.add(Restrictions.eq("pid", pid));列表结果 = cr.list();您还可以通过专业和用户之间的映射和 ER 关系更新问题吗?我相信它的 ManyToMany 映射在这种情况下你可以在你的代码本身中使用 EAGER FETCH,你将直接在 Profession 对象中获得关联的用户。
  • 你能解释一下在这种情况下什么是 REST 请求吗?
  • 你会回答控制。

标签: java spring hibernate jpa orm


【解决方案1】:

获取策略

有四种获取策略

  1. fetch-“join” = 禁用延迟加载,始终加载所有集合和实体。
  2. fetch-“select”(默认)= 延迟加载所有集合和实体。
  3. batch-size=”N” = 最多获取“N”个集合或实体,不记录
  4. fetch-“subselect” = 将其集合分组到 sub select 语句中。

详细解释可以查看Hibernate文档。

FetchType.LAZY 按需提供

FetchType.EAGER 是即时的

@SuppressWarnings({ "unchecked", "rawtypes" })
public List<User> getProfessionById(long id) throws Exception {
   session = sessionFactory.openSession();
       Criteria cr = session.createCriteria(Profession.class, "pro")
                             .setFetchMode("user", FetchMode.JOIN);
        cr.add( Restrictions.eq("uid", id));
        Profession pro = cr.uniqueResult();
        tx = session.getTransaction();
        session.beginTransaction();
        tx.commit();
    return pro.getUsers();
}  

【讨论】:

  • 兄弟我想获取职业表的ID,该职业与我从数据库中获取的职业相匹配
【解决方案2】:

我认为您在 DAO 类中做错了,因为您在方法中打开了会话和事务,并且可能在每个 DAO 方法中。

这样做会带来性能成本,并且在概念上很糟糕,事务必须将一组操作分组,这些操作将完全成功或失败。在大多数情况下,数据库事务将与整个业务操作相关,在这种情况下是 REST 请求。类似的东西

POST /用户

@RestController.createUser

开启交易

UserDAO.saveUser

提交交易

回复

此外,如果您查看您的代码,您正在打开一个交易然后关闭。

tx = session.getTransaction();
session.beginTransaction();
tx.commit();

在这种情况下,您正在查询数据库,因此根本不需要事务。

事务是您的应用程序中的一个横切关注点,鉴于您已经在使用 Spring,您应该查看 @Transactional 注释(或他的 xml 等价物)以使用 AOP 实现事务性(Spring 创建一个广泛的方面)。这将使您的代码更具可读性和可维护性。

@ManojP 的回答有好有坏。我认为您应该尽可能避免双向关系,因为这会使设计更加困难。我的建议是:始终从单向关系开始,如果您发现无法避免的情况,请使用它。好在他这样做时向你展示了懒惰的用途:

List<User> users = profession.getUsers();

这行代码应该在 DAO 之外。发生的情况是,您有一个标记为惰性的用户列表(默认),然后当您使用条件查询获取专业时,会触发对表专业的选择,并且每个专业对象都使用代理集合而不是真正的集合。当您调用professional.getUsers() 时,会在其中professional_id =professional.getId() 的用户表上触发一个新的选择。所以:

  1. 列出结果 = criteria.list();
  2. 从专业中选择 *
  3. professionalA.getUsers();
  4. Select * from User where professional_id = :professionA.getId()

但要小心!如果你有一系列职业(就像我认为你有,因为你要返回一个列表)并遍历每个职业并询问你将要做什么的用户列表:

  1. 列出结果 = criteria.list();
  2. 从专业中选择 *
  3. 每个职业 -> 职业.getUsers
  4. Select * from User where professional_id = :professionA.getId()
  5. Select * from User where professional_id = :professionB.getId()

这将有一个糟糕的表现。

@farvilin 的回答很好。但是在这种情况下,您将始终使用用户集合检索 Profession,因为在您的 DAO 中您始终使用 FetchMode.JOIN,然后您将失去惰性的好处。因此,如果您在查询 Professions 时总是想要用户列表,请对 Users 集合使用 lazy=true,但要注意可能产生的成本(如果 User 在查询 Professions 时有非惰性集合 A您还将检索用户列表和集合 A)。 如果你不想要这个,也许你可以有这个签名:

public List<Profession> getProfessionById(Long id, FetchMode fetchMode) throws Exception 

这不是很好,但表明 DAO 客户端可以选择 fecth 模式。

【讨论】:

    【解决方案3】:

    使用ManyToMany映射来获取Professions,这样相关的Users也会来。检查这个link

    【讨论】:

      【解决方案4】:

      从您的代码开始

      @SuppressWarnings({ "unchecked", "rawtypes" })
      public List<Profession> getProfessionById(long id) throws Exception {
          session = sessionFactory.openSession();
          Criteria cr = session.createCriteria(Profession.class);
          cr.add(Restrictions.eq("uid", id));
          List results = cr.list();
          tx = session.getTransaction();
          session.beginTransaction();
          tx.commit();
          return results;
      }
      

      如果您使用 Spring,我首先建议您使用 Transactionnal 注解。它使代码更清晰。然后停止使用SuppressWarnings,它又丑又没用,你可以配置你的IDE来隐藏它们。

      @Transactionnal(readonly = true)
      public List<Profession> getProfessionById(long id) throws Exception {
          Criteria cr = session.createCriteria(Profession.class);
          cr.add(Restrictions.eq("uid", id));
          List results = cr.list();
          return results;
      }
      

      没有使用条件是流畅的 API 并且不创建局部变量的事实,现在不需要了。

      @Transactionnal(readonly = true)
      public List<Profession> getProfessionById(long id) throws Exception {
          return session
              .createCriteria(Profession.class)
              .add(Restrictions.eq("uid", id))
              .list();
      }
      

      现在让我们解决您的问题

      @Transactionnal(readonly = true)
      public List<Profession> getProfessionById(long id) throws Exception {
          return session
              .createCriteria(Profession.class)
              .add(Restrictions.eq("uid", id))
              .setFetchMode("users", FetchMode.JOIN)
              .list();
      }
      

      当然需要这一行的专业;

      public class Profession {
          [...]
      
          @OneToMany(mappedBy = "profession")
          private Set<Users> users = new HashSet<>();
      
          [...]
      }
      

      警告

      你不需要getter/setter,不需要的时候不要调用反向映射。

      一定不要使用List,Hibernate不太喜欢没有顺序声明的List。

      不要在实体中将用户声明为Eagerly fetched,每次加载大量用户拥有的职业时都会遇到可怕的问题,即使您自己不擅长获取他们。

      不要使用 FetchMode.EAGER,它已被弃用!

      【讨论】:

        【解决方案5】:

        首先你需要在 Profession 和 User 实体类中添加下面的映射

        职业课

        //bi-directional many-to-one association to Users
        @OneToMany(mappedBy="profession", cascade=CascadeType.ALL)
        private List<User> users;
        
        public List<User> getUsers() {
            return this.users;
        }
        
        public void setUsers(List<User> users) {
            this.users = users;
        }
        

        在用户实体类中

        @ManyToOne(fetch=FetchType.EAGER)
        @JoinColumn(name="profession_id")
        private Profession profession;
        

        然后您可以使用现有的 DAO 类代码按 id 获取职业对象。

        @SuppressWarnings({ "unchecked", "rawtypes" })
        public List<Profession> getProfessionById(long id) throws Exception {
            session = sessionFactory.openSession();
            Profession profession =  (Profession) session.get(Profession.class, id);
        
            // here you can get list of users for this profession 
            // List<User> users = profession.getUsers();
            return profession;
        }
        

        【讨论】:

          【解决方案6】:

          根据您的查询,Profession 表有一个 uid 列,这可能是 Users 表的 FK,我认为 Users 表应该有 Profession 的 FK。

          所以Users 表将与Profession 有一个many-to-one 关联:

          @ManyToOne
          private Profession profession;
          

          并且Profession 可以关联具有该特定职业的所有用户,因此在Profession 实体中,您具有此关联的反面:

          @OneToMany(mappedBy = "profession")
          private List<Users> users = new ArrayList<>();
          

          现在要让所有用户都拥有专业,您可以运行如下简单查询:

          List<Users> users = (ist<Users>) session.createQuery(
              "select u " +
              "from Profession p " +
              "join fetch p.users u " +
              "where p.id = :id")
          .setParameter("id", proffesionId)
          .list();
          

          【讨论】:

          • 我认为一个用户可以有很多职业,因为他在职业表中按 user_id 搜索时返回了一个 List
          【解决方案7】:

          您也许可以使用以下内容:

          Criteria cr = session.createCriteria(Profession.class);
          cr.setFetchMode("user", FetchMode.EAGER);
          cr.add(Restrictions.eq("uid", id));
          List results = cr.list();
          

          我没有使用过 Hibernate 标准,但快速 google 想出了类似的东西。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-07-18
            • 2015-11-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-10-09
            相关资源
            最近更新 更多