防止Dozer触发Hibernate延迟加载

发布于 2024-10-30 14:50:20 字数 1010 浏览 1 评论 0原文

我正在使用 Spring 事务,因此当 POJO 到 DTO 转换发生时事务仍然处于活动状态。

我想阻止 Dozer 触发延迟加载,以便永远不会发生隐藏的 sql 查询:所有获取都必须通过 HQL 显式完成(以获得对性能的最佳控制)。

  1. 这是一个好的实践吗(我在任何地方都找不到它的记录)?

  2. 如何安全地进行?

我在 DTO 转换之前尝试过此操作:

PlatformTransactionManager tm = (PlatformTransactionManager) SingletonFactoryProvider.getSingletonFactory().getSingleton("transactionManager");
tm.commit(tm.getTransaction(new DefaultTransactionDefinition()));

我不知道事务会发生什么,但 Hibernate 会话没有关闭,并且延迟加载仍然发生。

我尝试了这个:

SessionFactory sf = (SessionFactory) SingletonFactoryProvider.getSingletonFactory().getSingleton("sessionFactory");
sf.getCurrentSession().clear();
sf.getCurrentSession().close();

它可以防止延迟加载,但是直接在应用程序层(在我的项目中称为“facade”)中操作会话是一个好习惯吗?我应该担心哪些负面影响? (我已经看到涉及 POJO -> DTO 转换的测试无法再通过 AbstractTransactionnalDatasource Spring 测试类启动,因为此类尝试触发不再链接到活动会话的事务的回滚)。

我还尝试将传播设置为 NOT_SUPPORTED 或 REQUIRES_NEW,但它重用当前的 Hibernate 会话,并且不会阻止延迟加载。

I am using Spring transactions so the transaction is still active when POJO to DTO conversion occurs.

I would like to prevent Dozer from triggering lazy loading, so that hidden sql queries never occur : all fetching has to be done explicitly via HQL (to get the best control on performances).

  1. Is it a good practice (I can't find it documented anywhere) ?

  2. How to do it safely ?

I tried this before DTO conversion :

PlatformTransactionManager tm = (PlatformTransactionManager) SingletonFactoryProvider.getSingletonFactory().getSingleton("transactionManager");
tm.commit(tm.getTransaction(new DefaultTransactionDefinition()));

I don't know what happens to the transaction, but the Hibernate session doesn't get closed, and the lazy loading still occurs.

I tried this :

SessionFactory sf = (SessionFactory) SingletonFactoryProvider.getSingletonFactory().getSingleton("sessionFactory");
sf.getCurrentSession().clear();
sf.getCurrentSession().close();

And it prevents lazy loading, but is it a good practice to manipulate session directly in the application layer (which is called "facade" in my project) ? Which negative side effects should I fear ? (I've already seen that tests involving POJO -> DTO conversions could no more be launched through AbstractTransactionnalDatasource Spring test classes, because this classes try to trigger a rollback on a transaction which is no more linked to an active session).

I've also tried to set propagation to NOT_SUPPORTED or REQUIRES_NEW, but it reuse the current Hibernate session, and doesn't prevent lazy loading.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

不爱素颜 2024-11-06 14:50:20

我发现的管理此问题的唯一通用解决方案(在研究了自定义转换器、事件侦听器和代理解析器之后)是通过实现自定义字段映射器。我发现这个功能隐藏在 Dozer API 中(我不相信它记录在用户指南中)。

一个简单的例子如下;

public class MyCustomFieldMapper implements CustomFieldMapper 
{
    public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping) 
    {       
        // Check if field is a Hibernate collection proxy
        if (!(sourceFieldValue instanceof AbstractPersistentCollection)) {
            // Allow dozer to map as normal
            return false;
        }

        // Check if field is already initialized
        if (((AbstractPersistentCollection) sourceFieldValue).wasInitialized()) {
            // Allow dozer to map as normal
            return false;
        }

        // Set destination to null, and tell dozer that the field is mapped
        destination = null;
        return true;
    }   
}

这会将任何未初始化的 PersistentSet 对象返回为 null。我这样做是为了当它们传递给客户端时我可以区分 NULL(未加载)集合和空集合。这允许我在客户端中定义通用行为,以使用预加载的集,或进行另一个服务调用来检索该集(如果需要)。此外,如果您决定急切地加载服务层中的任何集合,那么它们将照常映射。

我使用 spring 注入自定义字段映射器:

<bean id="dozerMapper" class="org.dozer.DozerBeanMapper" lazy-init="false">
    <property name="mappingFiles">
        ...
    </property>
    <property name="customFieldMapper" ref="dozerCustomFieldMapper" />
</bean>
<bean id="dozerCustomFieldMapper" class="my.project.MyCustomFieldMapper" />

我希望这可以帮助任何寻找解决方案的人,因为我在搜索互联网时找不到任何示例。

The only generic solution I have found for managing this (after looking into Custom Converters, Event Listeners & Proxy Resolvers) is by implementing a Custom Field Mapper. I found this functionality tucked away in the Dozer API (I don't believe it is documented in the User Guide).

A simple example is as follows;

public class MyCustomFieldMapper implements CustomFieldMapper 
{
    public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping) 
    {       
        // Check if field is a Hibernate collection proxy
        if (!(sourceFieldValue instanceof AbstractPersistentCollection)) {
            // Allow dozer to map as normal
            return false;
        }

        // Check if field is already initialized
        if (((AbstractPersistentCollection) sourceFieldValue).wasInitialized()) {
            // Allow dozer to map as normal
            return false;
        }

        // Set destination to null, and tell dozer that the field is mapped
        destination = null;
        return true;
    }   
}

This will return any non-initialized PersistentSet objects as null. I do this so that when they are passed to the client I can differentiate between a NULL (non-loaded) collection and an empty collection. This allows me to define generic behaviour in the client to either use the pre-loaded set, or make another service call to retrieve the set (if required). Additionally, if you decide to eagerly load any collections within the service layer then they will be mapped as usual.

I inject the custom field mapper using spring:

<bean id="dozerMapper" class="org.dozer.DozerBeanMapper" lazy-init="false">
    <property name="mappingFiles">
        ...
    </property>
    <property name="customFieldMapper" ref="dozerCustomFieldMapper" />
</bean>
<bean id="dozerCustomFieldMapper" class="my.project.MyCustomFieldMapper" />

I hope this helps anyone searching for a solution for this, as I failed to find any examples when searching the Internet.

看轻我的陪伴 2024-11-06 14:50:20

上面流行版本的一个变体,确保捕获 PercientBag、PercientSets,凡是你能想到的......

public class LazyLoadSensitiveMapper implements CustomFieldMapper {

public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping) {
    //if field is initialized, Dozer will continue mapping

    // Check if field is derived from Persistent Collection
    if (!(sourceFieldValue instanceof AbstractPersistentCollection)) {
        // Allow dozer to map as normal
        return false;
    }

    // Check if field is already initialized
    if (((AbstractPersistentCollection) sourceFieldValue).wasInitialized()) {
        // Allow dozer to map as normal
        return false;
    }

    return true;
}

}

A variation on the popular version above, makes sure to catch both PersistentBags, PersistentSets, you name it...

public class LazyLoadSensitiveMapper implements CustomFieldMapper {

public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping) {
    //if field is initialized, Dozer will continue mapping

    // Check if field is derived from Persistent Collection
    if (!(sourceFieldValue instanceof AbstractPersistentCollection)) {
        // Allow dozer to map as normal
        return false;
    }

    // Check if field is already initialized
    if (((AbstractPersistentCollection) sourceFieldValue).wasInitialized()) {
        // Allow dozer to map as normal
        return false;
    }

    return true;
}

}

苏辞 2024-11-06 14:50:20

我没有让上面的工作(可能是不同的版本)。不过这工作正常

public class HibernateInitializedFieldMapper implements CustomFieldMapper {
    public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping) {
        //if field is initialized, Dozer will continue mapping
        return !Hibernate.isInitialized(sourceFieldValue));
    }
}

I didn't get the above to work (probably different versions). However this works fine

public class HibernateInitializedFieldMapper implements CustomFieldMapper {
    public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping) {
        //if field is initialized, Dozer will continue mapping
        return !Hibernate.isInitialized(sourceFieldValue));
    }
}
白色秋天 2024-11-06 14:50:20

您是否考虑过完全禁用延迟加载?

它似乎与您所说的想要使用的模式并不相符:

我想阻止Dozer触发延迟加载,这样隐藏的sql查询就永远不会发生:所有的获取都必须通过HQL显式完成(以获得对性能的最佳控制)。

这表明您永远不想使用延迟加载。

Dozer 和您传递给它的 Hibernate 支持的 Bean 彼此完全不了解; Dozer 所知道的是,它正在访问 bean 中的属性,并且 Hibernate 支持的 bean 正在响应对延迟加载集合的 get() 调用,就像您自己访问这些属性一样。

在我看来,任何让 Dozer 知道您的 Bean 中的 Hibernate 代理(反之亦然)的技巧都会破坏您的应用程序的各个层。

如果您不希望在意外时间触发任何“隐藏 SQL 查询”,只需禁用延迟加载即可。

Have you considered disabling lazy loading altogether?

It doesn't really seem to jive with the patterns you state you would like to use:

I would like to prevent Dozer from triggering lazy loading, so that hidden sql queries never occur : all fetching has to be done explicitly via HQL (to get the best control on performances).

This suggests you would never want to use lazy loading.

Dozer and the Hibernate-backed beans you pass to it are blissfully ignorant of each other; all Dozer knows is that it is accessing properties in the bean, and the Hibernate-backed bean is responding to calls to get() a lazy-loaded collection just as it would if you were accessing those properties yourself.

Any tricks to make Dozer aware of the Hibernate proxies in your beans or vice versa would, IMO, break down the layers of your app.

If you don't want any "hidden SQL queries" fired at unexpected times, simply disable lazy-loading.

爱*していゐ 2024-11-06 14:50:20

这个映射器的简短版本将是

return sourceFieldValue instanceof AbstractPersistentCollection && 
!( (AbstractPersistentCollection) sourceFieldValue ).wasInitialized();

Short version of this mapper will be

return sourceFieldValue instanceof AbstractPersistentCollection && 
!( (AbstractPersistentCollection) sourceFieldValue ).wasInitialized();
油焖大侠 2024-11-06 14:50:20

使用 CustomFieldMapper 可能不是一个好主意,因为它会调用源类的每个字段,但我们关心的只是惰性关联映射(子对象列表),因此我们可以在实体对象的 getter 中设置 null 值,

public Set<childObject> getChild() {
if(Hibernate.isInitialized(child){
    return childObject;
}else
 return null;
}

Using CustomFieldMapper may not be a good idea as it gonna invoke for every field of your source class,but our concern is only lazy association mapping(child object list),so we can set the null value in getter of the entity object,

public Set<childObject> getChild() {
if(Hibernate.isInitialized(child){
    return childObject;
}else
 return null;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文