5

I have a gigantic entity and I'd like to load its subset (ID and baz property):

@Entity
public class GiganticEntity {

    @Id Long id;

    @OneToOne(mappedBy = "giganticEntity")
    Foo foo;

    @OneToOne(mappedBy = "giganticEntity")
    Bar bar;

    @OneToOne(mappedBy = "giganticEntity")
    Baz baz;

    // default constructor + getters/setters

    public GiganticEntity(Long id, Baz baz) {
        this.id = id;
        this.baz = baz;
    }
}

I tried using following JPA query, but baz property will be null:

"SELECT new package.GiganticEntity(ge.id, ge.baz) " +
"FROM GiganticEntity ge WHERE ge.id = 1";

I tried adding an explicit join, but it resulted in null as well:

"SELECT new package.GiganticEntity(ge.id, b) FROM GiganticEntity ge " +
    "LEFT JOIN ge.baz as b " +
    "WHERE ge.id = 1";

If I only select my gigantic entity like this then everything works (but I am trying to save some joins):

"SELECT GiganticEntity g WHERE g.id = 1";

Is this achievable with JPA? I am using Hibernate as its implementation.

EDIT: Query actually needs to be LEFT JOIN, so I need all gigantic entites with baz-es.

Xorty
  • 16,617
  • 21
  • 99
  • 150

4 Answers4

2

Since GiganticEntity has an inverse one-to-one association to a Baz:

@OneToOne(mappedBy = "giganticEntity")
Baz baz;

It means that Baz has also an association to GiganticEntity:

@OneToOne
GiganticEntity giganticEntity;

The query can therefore become:

select new package.GiganticEntity(g.id, b)  
from Baz b
join b.giganticEntity g
where g.id : id

Edit

According to the question requirements changes:

Query actually needs to be LEFT JOIN, so I need all gigantic entites with baz-es.

You can map multiple entities to the same table. You will have the GiganticEntity containing all associations and several entity views:

@Entity
@Table(name="GiganticEntity")
@Immutable
public class GignaticBazViewEntity {

    @Id Long id;

    @OneToOne(mappedBy = "bar")
    Bar bar;

    @OneToOne(mappedBy = "baz")
    Baz baz;

    public GiganticEntity(Long id, Bar bar, Baz baz) {
        this.id = id;
        this.bar = bar;
        this.baz = baz;
    }
}

The query goes like this:

select g
from GignaticBazViewEntity g
left join fetch g.bar
left join fetch g.baz
where g.id : id

or

select g
from GignaticBazViewEntity g
FETCH ALL PROPERTIES
where g.id : id
Vlad Mihalcea
  • 103,297
  • 39
  • 432
  • 788
  • Thanks that'd work, but please check out my EDIT - I actually need all gigantic entities left join baz ... Anyway to do that with your approach? – Xorty Apr 20 '15 at 11:28
  • with this approach we'd end up loading all the eager dependencies of `GignaticEntity` - because `@OneToOne(mappedBy = "baz")` means that the original `Baz` class still has a reference to `GiganticEntity` and not to `GiganticViewEntity`. So I'd need to map "Baz" class twice as well unless there is some another workaround? – Xorty Apr 20 '15 at 13:57
  • Check my updated query. You can use the fetch directive. – Vlad Mihalcea Apr 20 '15 at 13:59
  • Thanks, but not quite what I am looking for. I was trying to say that `mappedBy = "baz"` means that other side of mapping is inside `GiganticEntity` and **not** inside `GiganticBazViewEntity`. Therefore it'll select stuff from the original mapping (joins I'm trying to avoid). – Xorty Apr 20 '15 at 14:21
  • MappedBy says that Bar has also a GiganticEntity association. You can have multiple mappings to the same tables and without joins it will have to execute secondary selects, which also works in my example if you removed the fetch directives. You should give it a try first. – Vlad Mihalcea Apr 20 '15 at 14:24
  • yeah so with the current approach it will have to perform **secondary selects** to the original `GiganticEntity` - which (as the name suggest) is ... quite gigantic. I am trying to avoid both unnecessary joins from the original query and obviously other unnecessary selects. Can you think of any other alternative? – Xorty Apr 20 '15 at 14:30
  • You either have joins of selects. There's no other alternative since you need to project a Child association. – Vlad Mihalcea Apr 20 '15 at 14:38
0

Why such stragne contruct? I would rather

"SELECT GiganticEntity ge LEFT JOIN FETCH ge.baz WHERE g.id = 1 ";

OR

"SELECT GiganticEntity ge FETCH ALL PROPERTIES WHERE g.id = 1 ";
Antoniossss
  • 24,977
  • 3
  • 43
  • 86
  • That's exactly what I am not trying to avoid :) "foo" and "bar" would also be eagerly fetched and I am trying to prevent that! – Xorty Apr 17 '15 at 21:54
  • Than `LEFT JOIN FETCH` to eagar fetch only baz. – Antoniossss Apr 17 '15 at 21:57
  • Unfortunately this happens if I try that: http://stackoverflow.com/questions/11164496/in-jpa-2-0-jpql-when-one-returns-a-new-object-how-may-one-make-use-of-fetch-jo – Xorty Apr 17 '15 at 22:11
0

If you don't want to always fetch a OneToOne (or a ManyToOne), you should explicitly declare them as lazy (default is eager). Change your code as follows:

@Entity
public class GiganticEntity {
    @Id Long id;

    @OneToOne(mappedBy = "giganticEntity", fetch = FetchType.LAZY)
    Foo foo;

    @OneToOne(mappedBy = "giganticEntity", fetch = FetchType.LAZY)
    Bar bar;

    @OneToOne(mappedBy = "giganticEntity", fetch = FetchType.LAZY)
    Baz baz;

    // default constructor + getters/setters
}

Then write your query to fetch what you wish:

SELECT GiganticEntity g LEFT JOIN FETCH g.baz WHERE g.id = 1
Didier L
  • 13,411
  • 5
  • 46
  • 90
  • Thanks for the suggestions, I fixed mappings in the question. This would be an obvious approach, but it won't work, because foo/bar/baz are optional relations (I should've specified that in the question). See more here -> http://stackoverflow.com/a/1445694/314073 – Xorty Apr 20 '15 at 16:35
  • Argh, I remember that one. However there are also some solutions there, like the bytecode instrumentation or the ugly `@OneToMany` workaround. – Didier L Apr 20 '15 at 16:56
0

The @OneToOne association must be defined as optional = false. See this question and answer

Yes, I know that sounds crazy, but it's the only way to tell Hibernate to create a Proxy for the associated Entity.

Community
  • 1
  • 1
DuncanKinnear
  • 4,241
  • 2
  • 31
  • 62
  • The problem here is, that it actually **is** an optional association. – Xorty Apr 23 '15 at 09:05
  • Sorry, you don't seem to be understanding. To make it lazy-loading you have to say that it is *NOT* optional, even though it is. I know that sounds back to front, but you need to read that other question and answer carefully to understand it. You are essentially 'fooling' Hibernate into thinking that it doesn't need to load the association, because you are telling it it must be there, so just create a proxy for it in the meantime. Try it. – DuncanKinnear Apr 23 '15 at 20:57
  • So, if you want to always load Baz, you need to make that association `optional = true`, and make Foo and Bar `optional = false`. – DuncanKinnear Apr 23 '15 at 21:07