如何使用Hibernate获取关联实体

8

我正在使用Spring Rest和Hibernate开发应用程序,我想从数据库获取嵌套记录,就像我为User获取Profession一样,现在我想获取与我之前获取的Profession相关联的User

这是我的Dao类:

@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;
}

Criteria cr = session.createCriteria(User.class); cr.add(Restrictions.eq("pid", pid)); List results = cr.list();另外,您能否更新一下问题,说明Profession和User之间的映射和ER关系?我认为这是ManyToMany映射,如果是这种情况,您可以在代码中使用EAGER FETCH,并直接获取与Profession对象相关联的Users。 - pratikpawar
在这种情况下,你能解释一下什么是REST请求吗? - gabrielgiussi
你会回答控制吗? - ooozguuur
7个回答

2

获取策略

有四种获取策略:

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

详细解释可查看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();
}  

1
兄弟,我想获取职业表中与我从数据库中获取的职业匹配的职业ID。 - java developer

2
根据您的查询,Profession表有一个名为uid的列,可能是指向Users表的外键。我认为Users表应该有一个指向Profession的外键。
因此,Users表将与Profession建立一个多对一的关联关系:
@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();

我认为一个用户可以有多个职业,因为当他在职业表中按照用户ID搜索时,他会返回一个List<Profession>。 - gabrielgiussi

2
首先,您需要在职业和用户实体类中添加以下映射。
在职业类中:
//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;
}

2
我认为你的DAO类中做错了一些事情,因为你在方法内部打开了一个会话和一个事务,可能是在DAO的每个方法内部都这样做。
这样做会有性能成本,并且从概念上讲也不好。一个事务必须将一组操作分组,这些操作将完全成功或失败。在大多数情况下,数据库事务将与整个业务操作相关联,在这种情况下是REST请求。比如:

POST /user

@RestController.createUser

打开事务

UserDAO.saveUser

提交事务

响应

此外,如果你查看你的代码,你是打开一个事务然后关闭它。
tx = session.getTransaction();
session.beginTransaction();
tx.commit();

在这种情况下,您正在查询数据库,因此根本不需要事务。
事务是应用程序中的横切关注点,鉴于您已经在使用Spring,您应该查看@Transactional注释(或其xml等效物)以实现AOP的事务处理(Spring创建一个环绕方面)。这将使您的代码更易读和可维护。
@ManojP的答案有好有坏。我认为您应该尽可能避免双向关系,因为它使设计更加困难。我的建议是:始终从单向关系开始,如果您发现无法避免某种情况,请使用它。好处是当他做以下操作时向您展示了惰性的使用:
List<User> users = profession.getUsers();

这行代码应该放在DAO的外面。当你使用条件查询获取职业时,由于用户列表被标记为延迟加载(即默认情况下),将触发对职业表的选择,并且每个职业对象都将用代理集合而不是真实集合构造。当你调用profession.getUsers()时,将会触发一个新的选择,该选择将从用户表中选择profession_id = profession.getId()的记录。因此:
  1. List results = criteria.list();
  2. Select * from Profession
  3. professionA.getUsers();
  4. Select * from User where profession_id = :professionA.getId()
但要注意!如果你有一个职业集合(我认为你有,因为你返回了一个List),并且遍历每个职业并询问用户列表,那么你将会做到:
  1. List results = criteria.list();
  2. Select * from Profession
  3. for each profession -> profession.getUsers
  4. Select * from User where profession_id = :professionA.getId()
  5. Select * from User where profession_id = :professionB.getId()
这将导致性能问题。
@farvilain的答案也很好。但在这种情况下,您将始终检索具有其用户集合的职业,因为在DAO中始终使用FetchMode.JOIN,然后会失去惰性的好处。因此,如果您在查询职业时始终需要用户列表,请对用户集合使用lazy=true,但要注意可能产生的成本(如果用户具有非惰性集合A,则在查询职业时还会检索用户列表加上集合A)。 如果不想要这个,也许您可以使用以下签名:
public List<Profession> getProfessionById(Long id, FetchMode fetchMode) throws Exception 

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

1
您可能会用到以下内容:
Criteria cr = session.createCriteria(Profession.class);
cr.setFetchMode("user", FetchMode.EAGER);
cr.add(Restrictions.eq("uid", id));
List results = cr.list();

我没有使用过Hibernate的criteria,不过快速搜索得到了类似的东西。


1
从你的代码开始。
@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时使用Transactional注释。它可以使代码更清晰。然后停止使用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,在不需要时不要调用反向映射。

请确保不要在实体中将用户声明为Eagerly获取,否则每次加载由许多用户拥有的职业时都会出现可怕的问题,即使您自己无法很好地获取它们。

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

不要使用FetchMode.EAGER,它已经过时了!


0
使用ManyToMany映射来获取职业,这样关联的用户也会一并出现。请查看link

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接