会话关闭后,如何禁用或禁止使用Hibernate代理?

时间:2019-05-28 14:21:58

标签: java hibernate jdbc

因此,我现在正在研究一个RESTful API,该API充当客户端和我们的数据库之间的一个层,我目前正在将Hibernate用于ORM,并且我已经尝试了好几天了,以找到解决问题的方法,到处都无济于事。

我不能发布我正在使用的实际代码,因为它太大了,但请想象一下:

我有一个类Employee映射为如下所示的Hibernate实体

@Entity
public class Employee {
    @Id
    Integer id;
    String name;
    Double salary;
    String department;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "company_id")
    Company company;

//    Getters/Setters ommitted


    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                ", department='" + department + '\'' +
                ", company=" + company +
                '}';
    }
}

另一个实体类Company如下所示:

@Entity
public class Company {

    @Id
    Integer id;
    String name;
    String address;
    @OneToMany(mappedBy = "company", fetch = FetchType.LAZY)
    List<Employee> employees;

//    Getters and setters ommited


    @Override
    public String toString() {
        return "Company{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", employees=" + employees +
                '}';
    }
}

数据库看起来像这样:

Employee:
| id | name     | salary | department | company_id |
|----|----------|--------|------------|------------|
| 1  | john doe | 3000   | Finance    | 1          |
| 2  | mary sue | 3300   | HR         | 1          |

Company:
| id | name | address    |
|----|------|------------|
| 1  | ACME | St. Sesame |

现在可以说我想从某个员工那里获取数据,如果以后需要公司数据,我只希望员工数据+公司ID(也只有ID)作为参考。看起来像这样:

Session session = SessionManager.openSession();

Employee emp = session.get(Employee.class, 1);

session.close();

现在我想显示员工数据

System.out.println(emp);

这里期望的输出(或至少是我期望的)是这样的:

Employee{id=1,name='john doe',salary=3000.0,department='Finance',company=Company{id=1,name=null,address=null,employees=null}}

这很简单,问题在于它将抛出LazyInitializationException,因为在Employee的toString()调用中,Hibernate尝试使用代理对象来获取公司的数据时,但会话已经存在关闭。

问题是,如果我在关闭会话的同时没有获取更多数据,那就没必要了,HIBERNATE!如果我打开一个会话,我将获取所有我需要的数据,并且仅获取我需要的数据,并且在我关闭它之后,这意味着我不再需要任何数据,谢谢hibernate ,但是即使在会话关闭后,Hibernate的代理对象仍然保持活动状态。

即使在StackOverflow上,我也一直在网上搜索,并且有一些所谓的“解决方案”或“解决方法”,例如:

  • 您为什么不将HQL与构造函数一起使用?

当查询足够简单以使用HQL时,我实际上在许多用例中都使用了那些。问题在于许多用例需要复杂的查询,具有大量的联接和计算,甚至使用未映射到Hibernate Entities的表,因此我需要一个本机查询来实现。

  • 您可以将HQL与构造函数一起使用,并为需要在查询中访问的每个数据库表创建实体

那将意味着映射我将用在一个,也许两个用例上的数十个实体,并且再也不会映射,只是这样我就可以将HQL与Constructor一起使用。此外,其中一些查询使用本机函数,而HQL根本不支持其他复杂的计算。

  • 然后使用本机查询,当您不希望使用该列数据时可以放置NULL as columnName

在某些用例上我也这样做,问题是当该列是实体关系上的联接列时,Hibernate设置了一个代理对象,而我不希望代理对象,只是联接列中的数据如果且仅在,则该代码的另一部分需要该数据,在这里我可以打开一个新会话并检索所需的数据。

  • 您为什么不使用视图打开会话模式来保持会话打开?

不能,那就是RESTful API,如果客户在第一次请求时要求雇员数据,然后以后再次请求该雇员的公司数据,该怎么办?在请求正文中传递公司ID?那是一个常见的用例。

  • session.detach()?

不起作用。

  • 无状态会话?

仍然创建代理对象,它仅禁用当前会话的第一级和第二级缓存。

  • 您为什么不使用DTO?

并为我的API的部分数据请求的每个用例创建一个新类?哈哈哈!

您看到的,我之所以使用像Hibernate这样的ORM解决方案的唯一原因是因为我不敢对ResultSet进行手动处理而死掉了。忘记HQL,Criteria API等。如果我只能让Hibernate为我映射ResultSet到对象(或对象列表),我不介意从字符串创建本机查询。我想让Hibernate知道,如果有一个我在会话关闭之前没有获取的实体关系,那意味着我不想获取它,所以没有代理对象。

我知道那里可能有另一个ORM解决方案不存在此问题,但是由于 DEADLINES ,我不能抽出时间来学习它,所以请有人帮助我。

编辑

因此,你们中的一些人将我的问题标记为Converting Hibernate proxy to a real entity object的重复项,但有区别:

  • 该问题围绕着如何“取消代理”对象,如如何触发Hibernate代理的初始化。

  • 我的问题是有关如何使Hibernate 对未获取的关系对象使用代理,而是仅使用具有其ID的关系类的实例。

  • p>

在我之前的示例中,我希望休眠后不使用代理来懒惰地获取与Company相关的Employee对象,而是希望创建一个new Company(company_id)像这样禁止代理

编辑2

我刚刚发现Jackson项目(用于在我的API中以JSON形式读取/写入数据)中有一个模块,该模块在序列化对象时会忽略Hibernate代理。

Maven依赖项:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
    <version>${jackson-version}</version>
</dependency>

然后只需在您的Object Mapper实例上注册该模块:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate5Module());

1 个答案:

答案 0 :(得分:0)

所以看来我的 EDIT 2 确实是解决我的问题的一种方法。

jackson-datatype-hibernate5最初会忽略未获取的代理,在序列化对象时将其设置为null,但是类Hibernate5Module(或您要使用的任何Hibernate模块版本)具有非常方便的配置这样杰克逊只能序列化未获取代理的ID,这正是我所寻找的。

// Configuring `ObjectMapper` and module:
ObjectMapper mapper = new ObjectMapper();
Hibernate5Module module = new Hibernate5Module()
    .configure(Hibernate5Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS, true);

mapper.registerModule(module);

这样,您可以使用该ObjectMapper为您的类创建ObjectReaders,这些类将不会在序列化时尝试初始化代理对象,而仅序列化对象的ID(如果它是OneToOne或ManyToOne关系,当然。)

我说这是“一种”解决方案,因为Hibernate仍在使用代理对象进行未获取的关系(当我只想使用代理对象的ID值供以后使用)时,唯一的一件事情是我已将Jackson配置为保留ID值并忽略代理对象。