如何使用Hibernate/ORM保持干净的层分离?

2022-09-03 06:31:07

如何使用Hibernate / ORM(或其他ORM...)保持干净的层?

我所说的干净层分离是指将所有Hibernate的东西都保存在DAO层中。

例如,在创建大型CSV导出流时,我们经常应该执行一些休眠操作,例如逐出以避免OutOfMemory...输出流的填充属于视图,但逐出属于 DAO。

我的意思是,我们不应该把逐出操作放在前端/服务中,我们也不应该把业务逻辑放在DAO中......因此,在这种情况下,我们能做些什么呢?

在许多情况下,您必须执行一些操作,例如逐出,刷新,清除,刷新,特别是当您玩一些交易,大数据或类似的事情时...

那么,如何使用像Hibernate这样的ORM工具来保持清晰的层分离呢?


编辑:我不喜欢在工作中两件事,因为我们有一个自定义的抽象DAO,它允许服务给出一个Hibernate标准作为参数。这是实用的,但对我来说,理论上调用此DAO的服务不应该知道标准。我的意思是,我们不应该以任何方式将Hibernate的东西导入到业务/视图逻辑中。


有没有一个答案,简单还是其他?


答案 1

如果“干净”的意思是上层不知道下层的实现,你通常可以应用Tell,不要问原则。对于 CSV 流式处理示例,它类似于以下内容:

// This is a "global" API (meaning it is visible to all layers). This is ok as
// it is a specification and not an implementation.
public interface FooWriter {
    void write(Foo foo);
}

// DAO layer
public class FooDaoImpl {
    ...
    public void streamBigQueryTo(FooWriter fooWriter, ...) {
        ...
        for (Foo foo: executeQueryThatReturnsLotsOfFoos(...)) {
            fooWriter.write(foo);
            evict(foo);
        }
    } 
    ...
}

// UI layer
public class FooUI {
    ...
    public void dumpCsv(...) {
        ...
        fooBusiness.streamBigQueryTo(new CsvFooWriter(request.getOutputStream()), ...);
        ...
    }
}

// Business layer
public class FooBusinessImpl {
    ...
    public void streamBigQueryTo(FooWriter fooWriter, ...) {
        ...
        if (user.canQueryFoos()) {
            beginTransaction();
            fooDao.streamBigQueryTo(fooWriter, ...);
            auditAccess(...);
            endTransaction();
        }
        ...
    }
}

通过这种方式,您可以自由地处理特定的ORM。这种“回调”方法的缺点是:如果您的层位于不同的JVM上,那么它可能不是很可行(在本例中,您需要能够序列化)。CsvFooWriter

关于通用DAO:我从未觉得有必要,我发现的大多数对象访问模式都不同到足以使特定的实现变得可取。但是,当然,进行层分离和强制业务层创建Hibernate标准是矛盾的路径。我会在DAO层中为每个不同的查询指定不同的查询方法,然后我会让DAO实现以它可能选择的任何方式(条件,查询语言,原始SQL等)获取结果。因此,而不是:

public class FooDaoImpl extends AbstractDao<Foo> {
    ...
    public Collection<Foo> getByCriteria(Criteria criteria) {
        ...
    }
}

public class FooBusinessImpl {
    ...
    public void doSomethingWithFoosBetween(Date from, Date to) {
        ...
        Criteria criteria = ...;

        // Build your criteria to get only foos between from and to

        Collection<Foo> foos = fooDaoImpl.getByCriteria(criteria);
        ...
    }

    public void doSomethingWithActiveFoos() {
        ...
        Criteria criteria = ...;

        // Build your criteria to filter out passive foos

        Collection<Foo> foos = fooDaoImpl.getByCriteria(criteria);
        ...
    }
    ...
}

我会做的:

public class FooDaoImpl {
    ...
    public Collection<Foo> getFoosBetween(Date from ,Date to) {
        // build and execute query according to from and to
    }

    public Collection<Foo> getActiveFoos() {
        // build and execute query to get active foos
    }
}

public class FooBusinessImpl {
    ...
    public void doSomethingWithFoosBetween(Date from, Date to) {
        ...      
        Collection<Foo> foos = fooDaoImpl.getFoosBetween(from, to);
        ...
    }

    public void doSomethingWithActiveFoos() {
        ...
        Collection<Foo> foos = fooDaoImpl.getActiveFoos();
        ...
    }
    ...
}

虽然有人可能会认为我正在将一些业务逻辑向下推到DAO层,但对我来说,这似乎是一个更好的方法:将ORM实现更改为替代方法会更容易。想象一下,例如,出于性能原因,您需要使用原始JDBC读取s来访问某些特定于供应商的扩展:使用通用DAO方法,您需要更改业务层和DAO层。使用这种方法,您只需重新实现 DAO 层即可。Foo


答案 2

好吧,你总是可以告诉你的DAO层在你想要的时候做它需要做的事情。拥有像DAO层中这样的方法,或类似的东西(甚至是针对不同对象的一组这些方法),对我来说并不是一个坏习惯。cleanUpDatasourceCache

然后,您的服务层能够调用该方法,而无需对后台的DAO所执行的操作进行任何假设。使用直接 JDBC 调用的特定实现在该方法中不执行任何操作。


推荐