Spring Java 中许多 DAO 的策略

Posted

技术标签:

【中文标题】Spring Java 中许多 DAO 的策略【英文标题】:Strategy for many DAOs in Spring Java 【发布时间】:2012-11-24 20:43:31 【问题描述】:

我们在现有项目中有许多 DAO(目前没有接口,但可以改变)。我们不是为每个 DAO 类连接一个 Spring 管理的 bean 并将它们注入到服务层,而是有一个类似这样的 DAO“工厂”:

public class DAOFactory 
private static DAOFactory daoFac;

static
    daoFac = new DAOFactory();


private DAOFactory()

public static DAOFactory getInstance()
    return daoFac;


public MyDAO1 getMyDAO1()
    return new MyDAO1();


    public MyDAO2 getMyDAO2()
    return new MyDAO2();

    ...

(注意 MyDAO1 和 MyDAO2 是具体的类)

这使我们能够轻松地在服务层中添加/调用 DAO 方法,而无需 1.) 将 DAO 接口作为属性添加到服务类 2.) 通过配置将 DAO 实现连接到服务方法中。 (而且我们有时在一个服务类中使用多个 DAO)。

DAOFactory.getInstance().getMyDAO1().doSomething();

到目前为止,这个策略对我们很有效(我们不需要太多切换实现),但我想知道如果我们能够重新开始,是否有更好的方法?我将 DAO 视为 bean 的自动装配,但我仍然需要在每个服务类中创建属性来表示正在使用的那些 DAO。在一个大型项目中,无论如何我都不愿意开始自动装配 bean - 我们需要为所有开发人员提供可见性。

感觉就像我在 a.) 与实现紧密耦合,但代码/配置开销较少和 b.) 与接口松散耦合,但需要大量代码/配置开销之间来回切换。

我错过了更好的方法吗?中间有什么?欢迎发表意见。

【问题讨论】:

如果你不喜欢依赖注入,为什么要使用 Spring?只需在服务类中自动装配 DAO:这就是 Sring 和 DI 的全部意义所在。然后通过注入模拟 DAO 为您的服务编写单元测试,并考虑测试它比使用静态工厂容易得多。 我们有一个与您在这里尝试的类似的类,但正如 JB Nizet 所提到的,我们将 DAO @Autowired 放入其中。自动装配 DAO 的一个关键是 Spring 默认在单例中管理它们,因此您不会创建大量对象。每次您需要使用给定的 DAO 时,您的代码示例都会创建每个 DAO 的新副本,并且应该不需要这样做。 @JB Nizet:我们大量使用 DI,只是不用于 DAO。这个方法不是我想出来的,但我有机会重做它,所以问题来了。 @Marvo:关于 new() 与单例的好点 【参考方案1】:

我将把所有的 DAO 作为 Spring 管理的组件,并将它们注入到服务中以实现松散耦合。为什么你认为自动装配 bean 在大项目中不好?

只需使用 @Component 注释每个 DAO 类 并用

替换 MyDao mydao = factory.getmyDao()

@Autowired MyDao myDao;

我没有看到太多的编码/配置开销。

【讨论】:

【参考方案2】:

到目前为止,我对我的项目采取了几种不同的方法,但还没有真正确定什么是“最好的”。可能没有“最佳”,但可能是“最适合您的需求”。

首先,我选择了一个基础服务类。

public abstract BaseService 
    @Autowired FooDAO fooDao;
    @Autowired BarDAO barDao;
    . . .
    . . .
    protected getFooDAO() 
        return this.fooDao;
    

然后在我的服务类中,我可以简单地写

Foo foo = getFooDAO().uniqueById(id);

这很有效,并且它使我的服务从 dao 实例变量的所有自动装配和访问器类中保持整洁。问题是,我现在在每个服务中都有这个基类的副本,坦率地说,嗯,这没什么大不了的。但它也会产生一种代码异味,因为它没有使用组合而不是继承,从本质上讲,DI 的一个原因是鼓励组合。

一位同事推荐了一家像您这样的工厂,并称其为 ServiceProvider。我们将其自动连接到我们的服务中。

@Component
public class ServiceProvider 
    @Autowired FooDAO fooDao;
    public FooDAO getFooDAO() 
        return this.fooDao;
    
    . . .
    // yadda yadda

那么我们就有了你所拥有的东西:

Foo foo = getServiceProvider().getFooDAO().uniqueById(id);

这实在是太丑陋了,而且确实不利于清晰。因此,我们只使用了提供者的实例,并将其命名为类似 sp 的简短而甜美的名称。然后我们得到

Foo foo = this.sp.getFooDAO().uniqueById(id);

再一次,它有效。这可能是一个更好的设计。而且我们只在一个地方自动连接 DAO,而不是连接到每个服务中,尽管这两种方式都不是什么大问题。但它让我感觉更好,即使 我感觉更好 不是项目要求(但你不认为它应该是吗?)

我一直在想我们会将两者结合起来。我们将更改 BaseService 以自动装配 ServiceProvider,然后包装丑陋的调用。

public abstract BaseService 
    @Autowired ServiceProvider serviceProvider;

    protected getFooDAO() 
        return this.serviceProvider.getFooDAO();
    

    protected getBarDAO() 
        return this.serviceProvider.getBarDAO();
    

在我的服务中提供更好的速记,不需要我将每个 DAO 自动连接到每个服务中知道,这是一个完全可笑的问题。

我剩下的问题是在调试器中单步执行代码。进出每个 getWhateverDAO() 调用都很乏味,通过 getServiceProvider() 添加一个可能的步骤也无济于事。

但这就是我处理这个问题的地方。坦率地说,我想我花了很多时间来思考这个问题,因为这是避免我们的应用程序带来的所有真正难题的好方法。

【讨论】:

【参考方案3】:

好问题。

我觉得很遗憾你开始使用DAOFactory。 Spring 是一个超级灵活的工厂,所以我真的不明白你为什么需要另一个。 Spring 中的自动装配有很多优点,并且不需要接口,因此您可以轻松切换到使用 Spring 访问 DAO。恕我直言,它不会减少但会提高其他开发人员的可见性。

此外,如果您正在考虑重构 DAO 层,请查看 Google 代码中的 GenericDAO:http://code.google.com/p/hibernate-generic-dao/

我对这个库有很好的体验。它可以节省您的时间。你实际上并不需要很多 DAO。你只需要一个 DAO。您显然可以从 google 代码包装通用 DAO 并添加您的应用程序特定的术语和功能。但不要在那里添加实体特定的代码。实体特定代码应该在服务层。如果您使用 Hibernate 标准 API,则不再有脆弱的 HQL,也不会与 hibernate 耦合。这个库支持 Hibernate 和 JPA,它的 API 非常简单和强大。

【讨论】:

【参考方案4】:

如果您在 1 项服务中使用过多的 DAO,您应该考虑将 1 项服务拆分为更低级别(细粒度)的服务

【讨论】:

【参考方案5】:

如果您不想注释您的 DAO 类或有像 @Value 这样的配置注释污染它们,我看到两个选项:

1.使用 DAO @Beans 创建一个@Configuration

@Configuration
public class DaoConfiguration 

    @Value("$db.name")
    private String dbName;

    @Value("$foo.table")
    private String fooTable;

    @Value("$bar.table")
    private String barTable;

    @Bean
    private FooDao fooDao() 
        return new FooDao(dbName, fooTable);
    

    @Bean
    private BarDao barDao() 
        return new BarDao(dbName, barTable);
    

然后为您需要的 DAO 创建一个 @Autowired 字段:

@Autowired
private FooDao fooDao;

2。创建DAO工厂@Component

如果您需要在 DAO 被销毁时进行一些清理,这很有用。

@Component
public class DaoFactory 

    @Value("$db.name")
    private String dbName;

    @Value("$foo.table")
    private String fooTable;

    @Value("$bar.table")
    private String barTable;

    private FooDao fooDao;
    private BarDao barDao;

    @PostConstruct
    public void init() 
        fooDao = new FooDao(dbName, fooTable);
        barDao = new BarDao(dbName, barTable);
    

    @PreDestroy
    public void destroy() 
        try 
            fooDao.close();
         catch (Exception e) 
            log.error("Failed to clean up FooDao", e);
        
        try 
            barDao.close();
         catch (Exception e) 
            log.error("Failed to clean up BarDao", e);
        
    

    public FooDao fooDao() 
        return fooDao;
    

    public BarDao barDao() 
        return barDao;
    

然后在你需要 DAO 的类中为工厂创建一个 @Autowired 字段:

@Autowired
private DaoFactory daoFactory;

并将其用作:

daoFactory.barDao().findAll();

【讨论】:

以上是关于Spring Java 中许多 DAO 的策略的主要内容,如果未能解决你的问题,请参考以下文章

使用Spring Data JPA的Spring Boot

Spring ORM数据访问——概述

Spring ORM数据访问——概述

Spring ORM数据访问——概述

springboot 整合 mybatis dao一直自动注入失败

Spring @注解