Java 休眠一对一:getId(),而不获取整个对象

Java 休眠一对一:getId(),而不获取整个对象,java,hibernate,jpa,Java,Hibernate,Jpa,我想在不加载整个对象的情况下获取一对一关系的id。我想我可以通过如下方式使用惰性加载来实现这一点: class Foo { @OneToOne(fetch = FetchType.LAZY, optional = false) private Bar bar; } Foo f = session.get(Foo.class, fooId); // Hibernate fetches Foo f.getBar(); // Hibernate fetches full

我想在不加载整个对象的情况下获取一对一关系的id。我想我可以通过如下方式使用惰性加载来实现这一点:

class Foo { 
    @OneToOne(fetch = FetchType.LAZY, optional = false)
    private Bar bar; 
}


Foo f = session.get(Foo.class, fooId);  // Hibernate fetches Foo 

f.getBar();  // Hibernate fetches full Bar object

f.getBar().getId();  // No further fetch, returns id
我希望f.getBar()不触发另一次提取。我希望hibernate为我提供一个代理对象,允许我调用.getId(),而无需实际获取Bar对象


我做错了什么

您可以使用HQL查询。getBar()方法将真正返回一个代理,在调用某个数据绑定方法之前不会获取该代理。我不确定你到底有什么问题。你能给我们提供更多的背景资料吗?

使用财产访问策略

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@AccessType("property")
protected Long id;
而不是

@OneToOne(fetch=FetchType.LAZY, optional=false)
private Bar bar;
使用

现在它工作得很好

如果调用的任何方法不是标识符getter方法,则会初始化代理。但它只在使用财产访问策略时起作用。记住这一点


请参阅:

仅添加到Arthur Ronald F D Garcia的帖子中:您可以通过
@access(AccessType.property)
(或不推荐的
@AccessType(“property”)
)强制访问属性,请参阅

另一种解决方案可能是:

public static Integer getIdDirect(Entity entity) {
    if (entity instanceof HibernateProxy) {
        LazyInitializer lazyInitializer = ((HibernateProxy) entity).getHibernateLazyInitializer();
        if (lazyInitializer.isUninitialized()) {
            return (Integer) lazyInitializer.getIdentifier();
        }
    }
    return entity.getId();
}

也适用于分离的实体。

在org.hibernate.Session中,您有一个不用延迟加载实体的函数:

公共可序列化getIdentifier(对象对象)抛出HibernateeException

可在hibernate 3.3.2.GA中找到:

public Serializable getIdentifier(Object object) throws HibernateException {
        errorIfClosed();
        checkTransactionSynchStatus();
        if ( object instanceof HibernateProxy ) {
            LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer();
            if ( li.getSession() != this ) {
                throw new TransientObjectException( "The proxy was not associated with this session" );
            }
            return li.getIdentifier();
        }
        else {
            EntityEntry entry = persistenceContext.getEntry(object);
            if ( entry == null ) {
                throw new TransientObjectException( "The instance was not associated with this session" );
            }
            return entry.getId();
        }
  }

Java Persistence with Hibernate一书在“13.1.3理解代理”中提到了这一点:

只要您只访问数据库标识符属性,就不会 代理的初始化是必要的。(请注意,事实并非如此 如果将标识符属性映射为直接字段访问,请休眠 然后甚至不知道getId()方法存在。如果调用它, 必须初始化代理。)

然而,基于本页中的@xmedeko answer,我开发了一个黑客程序,以避免在使用直接字段访问策略时初始化代理。只需修改
getId()
方法,如下所示

而不是:

    public long getId() { return id; }
使用:


这里的想法是将
getId()
方法标记为
final
,以便代理无法覆盖它。然后,调用该方法无法运行任何代理代码,因此无法初始化代理。该方法本身检查其实例是否为代理,在本例中,将从代理返回id。如果实例是真实对象,它将返回id。

更改getter方法,如下所示:

public Bar getBar() {
    if (bar instanceof HibernateProxy) {
        HibernateProxy hibernateProxy = (HibernateProxy) this.bar;
        LazyInitializer lazyInitializer = hibernateProxy.getHibernateLazyInitializer();
        if (lazyInitializer.getSession() == null)
            bar = new Bar((long) lazyInitializer.getIdentifier());
    }

    return bar;
}
添加@AccessType(“属性”)


现在这里有一个jackson hibernate数据类型库:

您可以配置以下功能:

 Hibernate4Module hibernate4Module = new Hibernate4Module();
 hibernate4Module.configure(Hibernate4Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS, true);

这将包括延迟加载关系的id-

不幸的是,接受的答案是错误的。其他答案也不能提供最简单或明确的解决方案

使用
类的
ID
的属性访问级别

@Entity
public class Bar {

    @Id
    @Access(AccessType.PROPERTY)
    private Long id;

    ...
}

就这么简单:)

谢谢您的回复。你所描述的并不是发生了什么。getBar()正在导致进行提取。正如您所描述的,我希望返回一个代理对象,而不执行获取。是否有其他配置可能丢失?实际上,getBar()后面的getId()导致实体被提取。您没有丢失任何配置。可能像“从Foo f中选择f.bar.id,其中f.id=?”这样的查询会帮您解决问题。代理对象不应该在bar.getId()上获取完整的bar。它已经知道id,因为这是Foo的一部分。无论如何,它在不调用.getId()的情况下执行fetch。使用@ManyToOne(fetch=FetchType.LAZY,optional=false)单值关联的相同行为对我来说并不顺利。这是一个Hibernate错误:另请参阅比较字段或属性访问:+1。如果没有会话,它就不能工作,例如对于分离的实体。这是否意味着我必须更改我的实体,使其在属性级别具有所有注释才能工作?如果我保持原样,将一对一注释移动到属性级别,并将访问类型设置为属性,则它不会work@shane谢谢你的回复。我不信任在我的项目中执行的查询,所以我添加了自己的db集成测试来验证。我现在可以用了。唯一的更改是在目标实体的id上添加访问类型。这是唯一需要的改变@Id@GeneratedValue(strategy=GenerationType.SEQUENCE,generator=“FILECONTENT\u Id\u SEQ”)@SequenceGenerator(name=“FILECONTENT\u Id\u SEQ”,sequenceName=“FILECONTENT\u Id\u SEQ”)@Column(name=“Id”,nullable=false)@Access(AccessType.PROPERTY)私有长Id;除了identity/identifier getter方法之外,在没有完全对象检索/代理初始化的情况下,在字段中是否有其他解决方法?PatrickM,不使用此功能。您可以创建映射到同一个表的不同简化实体。我使用了您的想法,加上代理无法覆盖最终方法的事实,来更改
getId()
方法本身以避免初始化。如果可以的话,请看我在这一页的答案,告诉我你的想法。我也不明白你为什么要检查
lazyInitializer.isUninitialized()
。当实体是HibernateProxy时,是否总是返回
lazyInitializer.getIdentifier()
?我不记得为什么使用
if(lazyInitializer.isUninitialized())
。也许只有在真正必要的时候才使用肮脏的伎俩。我认为它可能会被省略。Hibernate注释“AccessType”已被弃用。改用JPA2注释
@Access(AccessType.PROPERTY)
不推荐使用Hibernate注释“AccessType”。改用JPA2注释:
@Access(AccessType.PROPERTY)
哈哈,这真是一个可怕的黑客:)“不要在家里做”@OndraŽižka你错了。这段代码工作得很好。而且,它没有违反任何规则,有
 Hibernate4Module hibernate4Module = new Hibernate4Module();
 hibernate4Module.configure(Hibernate4Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS, true);
@Entity
public class Bar {

    @Id
    @Access(AccessType.PROPERTY)
    private Long id;

    ...
}