在Spring-Hibernate项目中,初始化实体(POJO)集合的正确方法是什么?

7
我有一个POJO类,叫做Foo,它有一个其他实体实例的Set,叫做bars。此外,还有为这个项目编写的标准杂项类:Foo和Bar的service和dao。
我想让BarService获取与某些Foo相关联的Bar实例的Set。现在我有以下代码,我认为在概念上是不好的。
 
public class Foo {
    Set<Bar> bars;

    public Set<Bar> getBars() {
        if (bars == null)
            return ( bars = new HashSet() );
        return bars;
    }
}

 
public class BarServiceImpl {
    public List<Bar> getListOfBars(Foo foo) {
        return new ArrayList(foo.getBars());
    }
}

有三个问题: 1. 在哪里更好地初始化Foo的Set? 2. 为此类目的使用哪些特定的Sets和Lists更好? 3. 我当前的实现存在哪些概念问题,如何做得更好?

提前感谢。

3个回答

13

在哪里初始化Foo的Set更好?

大多数情况下,我会在声明集合时进行初始化,这也是Hibernate推荐的做法。引用文档:

6.1. Persistent collections

Hibernate requires that persistent collection-valued fields be declared as an interface type. For example:

public class Product {
    private String serialNumber;
    private Set parts = new HashSet();

    public Set getParts() { return parts; }
    void setParts(Set parts) { this.parts = parts; }
    public String getSerialNumber() { return serialNumber; }
    void setSerialNumber(String sn) { serialNumber = sn; }
}

The actual interface might be java.util.Set, java.util.Collection, java.util.List, java.util.Map, java.util.SortedSet, java.util.SortedMap or anything you like ("anything you like" means you will have to write an implementation of org.hibernate.usertype.UserCollectionType.)

Notice how the instance variable was initialized with an instance of HashSet. This is the best way to initialize collection valued properties of newly instantiated (non-persistent) instances. When you make the instance persistent, by calling persist() for example, Hibernate will actually replace the HashSet with an instance of Hibernate's own implementation of Set.

如果将其保留为null是您业务的一部分,我的建议是在(通用的)链接管理方法中初始化它:
public class Foo {
    ...
    private Set<Bar> bars;
    ...
    public void addBar(Bar bar) {
        if (this.bars == null) {
            this.bars = new HashSet<Bar>();
        }
        this.bars.add(bar);
    }
}

哪种特定的Set和List更适合这些目的?

这完全取决于您需要的语义。 Set 不允许重复,List 允许重复,并引入了位置索引。

我的当前实现有哪些概念问题,如何改进?

  1. 不会在getter中执行赋值操作。
    • 如果此时集合应该是 null,就让它保持为 null
  2. 我看不出你的服务有什么增加的价值
    • 为什么不直接调用 foo.getBars()
    • 为什么要转换集合?

需要转换集合来实现JSF视图层,但如果我想使用带有set的datatable,则必须实现自定义数据模型。 - edio
关于“为什么不直接调用foo.getBars()?”这样做不好吗?我的视图层将直接与实体通信,而不是与其服务通信?至于在声明点初始化:每次创建实体时,不是会首先为空集合分配内存,然后再为从数据库检索到的集合分配内存,这样不好吗? - edio
“我的视图层直接与实体进行通信,这不是很糟糕吗?” “不是的,除非你喜欢把事情搞得比必要的更复杂。” “每次创建实体时都会先分配空集合的内存,再分配从数据库检索到的集合的内存,这不是很糟糕吗?” “伙计,这应该是你最不用担心的事情了。” - Pascal Thivent
过去我在声明this.bars = new HashSet<Bar>()的get方法时遇到了问题。Hibernate似乎无法“拥有”这个集合,导致我改为在对象创建时初始化集合。那是很久以前的事情了,现在我不确定这是否仍然是一个问题。 - Marc

2
您的实体可以是一组具有getter和setter的字段。您需要注意的是如何关联您的对象以及您采取的填充对象的方法。
ORM API提供了使用对象而不是SQL的自由。在选择初始化对象的字段时,您应该谨慎。例如,如果您有一个人对象,其中包括姓名、年龄、联系人集合和访问过的城市。在您只对该人的姓名和年龄感兴趣而不关心联系人和城市的情况下,您应该仅加载姓名和年龄。这意味着联系人和城市应该是延迟加载的。
当您对联系人感兴趣时,您只需加载联系人而不是整个人对象或通过人对象。您希望仅使用Dao/Service加载联系人集,并明确定义加载对象特定方面的方法(使用反向关联)。
一些最佳的Hibernate实践可以在最佳实践中找到。 更新: 1)实体本身不会自动填充。其中一种流行的方法是使用DAO来完成这项工作。您的实体可以很简单
public class Foo {
    private Set<Bar> bar=new HashSet<Bar>();
    public Set<Bar> getBar {
        return bar;
    }
    public void setBar(Bar bar) {
        this.bar = bar;
    }
}

2) 您还可以将事务管理在另一层中,也称为服务层。


谢谢。所以,如果我理解你的意思正确的话,我需要在DAO中定义一个方法,该方法将以用户ID作为参数,并执行一个Hibernate查询来加载所需的方面?对吗? - edio
没错。随着您对其了解的增加,可以阅读有关如何加载特定字段集、延迟加载、反向关联、条件和HQL的内容。我发现HQL非常有效,可以控制对象中加载的内容。根据情况,您可以选择加载哪些字段,从而避免不必要的SQL连接或n+1问题。 - ch4nd4n

1
我倾向于在服务层中初始化集合,同时也在此处处理事务。因此,我可以在我的BaseDAO中编写一个方法,通过反射来初始化项目中任何实体的任何集合,只需将要被急切获取(初始化)的集合名称传递到该方法中即可。
public <T extends Object> T getEntity(Class<T> clazz,long id,String[] collectionsToBeInitialized){
        T entity=(T) this.getCurrentSession().createCriteria(clazz).add(Restrictions.idEq(id)).setFetchMode(collectionsToBeInitialized[0], FetchMode.JOIN).uniqueResult();
        int length=collectionsToBeInitialized.length;
        for (int idx=1;idx<length;idx++){
            String collectionName=collectionsToBeInitialized[idx];
            try {
                Method m = clazz.getMethod("get" + collectionName.substring(0, 1).toUpperCase() + collectionName.substring(1),(Class<T>) null);
                Hibernate.initialize(m.invoke(entity,(Object[]) null));
            } catch (NoSuchMethodException e) {
                LOG.error("Could not initialize collection " + collectionName + " of class Event", e);
            } catch (InvocationTargetException e) {
                LOG.error("Could not initialize collection " + collectionName + " of class Event", e);
            } catch (IllegalAccessException e) {
                LOG.error("Could not initialize collection " + collectionName + " of class Event", e);
            }
        }
        return entity;
    }

然后,您可以使用此方法从服务层初始化任何集合:

MyEntity ent=getEntity(MyEntity.class,id,new String[]{"collection1","collection2"});

一个更详细的例子: http://objecthunter.congrace.de/tinybo/blog/articles/69

有用的代码片段,我肯定会在未来的项目中使用这种方法。但现在我正在开发一个非常小的自学项目,类似于Spring-hibernate的hello world,所以现在对我来说还是很复杂的。无论如何,谢谢。 - edio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接