OpenSessionInView 与交易? (Spring/Hibernate/JPA)

OpenSessionInView vs. Transactional? (Spring/Hibernate/JPA)

我有一个 JPA 实体,上面有延迟加载 collection。我不需要每次 collection。

@Entity(name = "Foo")
@Access(AccessType.FIELD)
@Table(name = "TEST", schema = "TEST")
public class Foo implements Serializable {
    private static final long serialVersionUID = 1L;

    @OneToMany(mappedBy="foo", targetEntity=Bar.class, fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    private List<Bar> bars;
}

@Entity(name = "Bar")
@Access(AccessType.FIELD)
@Table(name = "TEST", schema = "TEST")
public class Bar implements Serializable {
    private static final long serialVersionUID = 1L;

    @ManyToOne(targetEntity = Foo.class)
    @JoinColumn(name = "FOO_ID", referencedColumnName = "ID")
    private Foo foo;
}

我在服务 class 上有几个方法执行大量数据库交互,最后将 Foo 实体保存到数据库中。我需要在 collection.

中对大约 100 个项目进行此操作
@Service
public class FooService {

    @Autowired
    private FooRepository fooRepository;

    public void processAllFoos() {
        fooRepository.findAll().forEach(foo -> {
            processFoo(foo);
        });
    }

    private void processFoo(Foo foo) {
        foo.getBars().forEach(bar -> {
            // Do a lot of time consuming stuff here that involves
            // entities of other types and modify each bar object
        });
        fooRepository.save(foo);
    }
}

processAllFoos 在收到请求时从 @RESTController 调用。

但是,我不希望 processAllFoos 包含在单个数据库事务中,因为这会锁定整个 Foo table,直到对所有 Foos 执行业务逻辑。

如果我使用 processFoo 方法 @Transactional,我会得到 LazyInitializationException,它抱怨 Hibernate session 是 non-existent。为了完成这项工作,我需要在调用堆栈中创建所有方法 @Transactional,以便嵌套方法可以加入调用方法的事务。但这会锁定整个 Foo table 如上所述。

dispatcher servlet 添加 OpenSessionInViewFilter 解决了我的问题,但我读到性能和实体 detaching/reattaching 存在问题(我在应用程序的其他部分这样做) 用这种方法。

有没有一种方法可以在不使用 OpenSessionInView 方法的情况下做我想做的事?我使用这种方法添加了哪些其他漏洞?

Spring/Hibernate 4.x


根据下面的答案,我能够做到以下几点:

@Service
public class FooService {

    @Autowired
    private FooRepository fooRepository;

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void processAllFoos() {
        fooRepository.findAll().forEach(foo -> {
            transactionTemplate.execute(new TransactionCallback<Object>() {
                public Object doInTransaction(TransactionStatus status) {
                    try {
                        processFoo(foo);
                        status.flush();
                    } catch(Exception e) {
                        status.setRollbackOnly();
                    }
                    return null;
                }
            });
        });
    }

    private void processBar(Foo foo) {
        foo.getBars().foreEach(bar -> {
            // Do a lot of time consuming stuff here that involves
            // entities of other types and modify each bar object 
        });
        fooRepository.save(foo);
    }
}

OpenSessionInViewFilter常用于解决View层(UI组件或页面模板)的LazyInitialization问题,因为View层不能也不能直接管理事务。 在您的情况下,可以应用另一种获取所有 Bar 对象的方法。

首先 您获取所有 Foo 对象 ID 而不是获取完整对象。

第二 使用 Foo ids 集合迭代相关的 Bar 对象。

第三 如果您不想进行一笔大交易,那么您可以使用Spring 交易模板来显式管理交易。

您的代码示例可能如下所示:

@Service
public class FooService {

    @Autowired
    private FooRepository fooRepository;

    @Autowired
    private BarRepository barRepository;

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void processAllFoos() {
        final List < Long > fooIdList = transactionTemplate.execute(new TransactionCallback() {
            public Object doInTransaction(TransactionStatus status) {

                return fooRepository.findIdList();
            }
        });

        transactionTemplate.execute(new TransactionCallback() {
            public Object doInTransaction(TransactionStatus status) {
                barRepository.findByFooIdList(fooIdList).forEach(bar - > {
                    processBar(bar);
                });
                return null;
            }
        });

    }

    private void processBar(Bar bar) {
        // Do a lot of time consuming stuff here that involves
        // entities of other types and modify each bar object
        barRepository.save(bar);
    }
}

下面的示例显示了如何在不增加性能开销的情况下解决您的任务。但是你应该明白,如果FooBar table链接有外键约束,那么Foo table中的相关记录可能会被[=19阻塞=] 每次更新 Bar table.

中的行