-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 14 posts ] 
Author Message
 Post subject: Loading association Set via Criteria
PostPosted: Wed Oct 29, 2008 4:54 pm 
Beginner
Beginner

Joined: Fri Sep 12, 2008 10:55 am
Posts: 33
Code:
...
public class Primary
{
    private Set<Secondary> secondaries;
    ...
    @OneToMany( cascade = CascadeType.ALL, mappedBy = "primary" )
    public Set<Secondary> getSecondary()
    {
        return secondaries;
    }
}

...
public class Secondary
{
    @Column(...)
    private String statusCode;
    ...
    public Primary primary;
    ...
    @ManyToOne( fetch = FetchType.LAZY)
    @JoinColumn( ... )
    public Primary getPrimary()
    {  return primary }
}


I want to be able to query the Primary class, but the Set of secondaries, I only want based on a certain criteria - let's say a status code of 'A'.

Now, I have tried to use Critieria via:

Code:
Criteria criteria = getSession().createCriteria(Primary.class);
criteria.createCriteria("secondaries", "secondary");
criteria.add( Restrictions.eq("secondary.statusCode", "A" ) );

List<Primary> primaries = criteria.list();


The Query that this returns is 100% correct (assuming I typed my example above correctly).

Quote:
select p...., s... FROM PRIMARY p LEFT OUTER JOIN SECONDARY s
on ... WHERE ...


However, the Primary.secondaries object is never loaded up with the returned Secondary objects. It's a pretty simple way to restrict my primary query I guess - HOWEVER, when calling the primary.getSecondaries(), it now does a NEW query, and returns ALL of the rows. Assuming that I have rows with status' of A, I, and P!

How else can I get these associations loaded via criteria, and not just the whole thing?
[/quote]


Top
 Profile  
 
 Post subject:
PostPosted: Wed Oct 29, 2008 6:44 pm 
Newbie

Joined: Fri Jul 14, 2006 12:05 pm
Posts: 19
You need to filter the collection, something like the following:

... retrieve the list of primaries ...
Code:
List<Secondary> secondaries = session.createFilter(primary.getSecondary(), "where this.statusCode = 'A'").list();


The returned list is a completely independent collection, so any changes to the collection would not be persisted (although changes to Secondary's inside it would be).

I have no idea if this is possible using purely Criteria.

Hope this helps,
Oscar


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 30, 2008 1:11 pm 
Beginner
Beginner

Joined: Fri Sep 12, 2008 10:55 am
Posts: 33
In one respect that does work, but it is a work around that I don't want :)

I guess I want to be able to filter off of those OneToMany somehow. It seems really lame that I might have a history of records that could be 1..n, and yet the get on the OneToMany is going to retrieve all bazillion of them, when technically, a simple criteria might make it 1 record.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 30, 2008 1:44 pm 
Newbie

Joined: Fri Jul 14, 2006 12:05 pm
Posts: 19
I don't think my answer was sufficiently clear. I meant for you to retrieve the list of Primary's using your existing Criteria:

Code:
Criteria criteria = getSession().createCriteria(Primary.class);
criteria.createCriteria("secondaries", "secondary");
criteria.add( Restrictions.eq("secondary.statusCode", "A" ) );

List<Primary> primaries = criteria.list();


This retrieves only the Primary's that have Secondary's with the desired status code. Then, when processing the Primary's:
Code:
for (Primary primary : primaries) {
  List<Secondary> secondaries = session.createFilter(primary.getSecondary(), "where this.statusCode = 'A'").list();
  // do something with the Secondary's...
}


The entire collection of Secondary's is never loaded into memory, just the ones with statusCode = 'A'. The downside is you get a query for each Primary, but you'd get that anyway with FetchType.LAZY.

Hope that's a little clearer,
Oscar


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 30, 2008 1:55 pm 
Newbie

Joined: Fri Jul 14, 2006 12:05 pm
Posts: 19
I don't think my answer was sufficiently clear. I meant to retrieve the list of Primary's using your existing Criteria:

Code:
Criteria criteria = getSession().createCriteria(Primary.class);
criteria.createCriteria("secondaries", "secondary");
criteria.add( Restrictions.eq("secondary.statusCode", "A" ) );

List<Primary> primaries = criteria.list();


This retrieves only the Primary's that have Secondary's with the desired status code. Then, when processing the Primary's:
Code:
for (Primary primary : primaries) {
  List<Secondary> secondaries = session.createFilter(primary.getSecondary(), "where this.statusCode = 'A'").list();
  // do something with the Secondary's...
}


The entire collection of Secondary's is never loaded into memory, just the ones with statusCode = 'A'. The downside is you get a query for each Primary, but you'd get that anyway with FetchType.LAZY.

Hope that's a little clearer,
Oscar


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 30, 2008 4:50 pm 
Beginner
Beginner

Joined: Fri Sep 12, 2008 10:55 am
Posts: 33
Here is what I don't understand however:

Going to the basic criteria query:
Code:
Criteria criteria = getSession().createCriteria(Primary.class);
criteria.setFetchMode("secondaries", FetchMode.JOIN);

List<Primary> primaries = criteria.list();


This actually DOES get me a list of Primary objects with the Primary.secondaries fully loaded (in one query)...

Code:
SELECT this_.ID AS ID89_1_,
       secondarie2_.ID AS ID3_,
       secondarie2_.ID AS ID90_0_,
       secondarie2_.STATUS AS STATUS90_0_
  FROM    Primary this_
       LEFT OUTER JOIN
          Secondary secondarie2_
       ON this_.ID = secondarie2_.ID



However, what I do not understand is why I cannot add to the above:
Code:
criteria.createCriteria("secondaries", "secondary");
criteria.add( Restrictions.eq("secondary.statusCode", "A" ) );


It still creates the same query as above - with the added restriction on the JOIN; except one is INNER vs. OUTER (but know how to change with CriteriaSpecification)

Code:
SELECT this_.ID AS ID89_1_,
       secondary1_.ID AS ID90_0_,
       secondary1_.STATUS AS STATUS90_0_
  FROM    Primary this_
       INNER JOIN
          Secondary secondary1_
       ON this_.ID = secondary1_.ID
WHERE secondary1_.STATUS = 'A'


however, it doesn't actually load the secondaries object IN the Primary object. So what gives?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 30, 2008 5:03 pm 
Beginner
Beginner

Joined: Fri Sep 12, 2008 10:55 am
Posts: 33
well...since the post, I have found a solution! However, I would still like someone to explain something...

solution:
Code:
Criteria criteria = getSession().createCriteria(Primary.class);
criteria.createCriteria("secondaries", "secondary", CriteriaSpecification.LEFT_JOIN );
criteria.add( Restrictions.eq("secondary.statusCode", "A" ) );


the SQL performs a LEFT OUTER JOIN....


BUT (isn't there always one)

changing the CriteriaSpecification to .INNER_JOIN causes it to NOT load the secondary objects into Primary? So any subsequent call to getSecondaries() executes another query...

So the question is, why does LEFT_JOIN work differently than INNER_JOIN in this case ... BUG you say??

(losing hair...please help)


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 30, 2008 6:46 pm 
Newbie

Joined: Fri Jul 14, 2006 12:05 pm
Posts: 19
Without digging through the code for the Criteria functionality, I can't give a definitive answer, BUT, the fact is, if Hibernate returns a Primary object loaded with a partial set of Secondaries (just the ones that meet the statusCode criterion), then Hibernate is really returning incorrect information.

The "real" collection actually contains all the Secondaries, and not just the ones you're interested in right this moment. If Hibernate returns a collection that doesn't contain all the Secondaries, then it's lying. You would expect that you could add and delete Secondaries from that collection normally, and that Hibernate would persist them, but that wouldn't be valid in these circumstances. (Think about how hard it would be for Hibernate to know that a Secondary was already in the Set...)

So, I suspect (at least, I hope!) that Hibernate disables populating the secondaries collection when your SELECT doesn't return the full set of Secondary objects. (It would be a pretty big surprise if you thought you had a fully populated collection, but didn't.) If you want a partial set, Hibernate gives you the choice of calling filter() or filtering manually while you're iterating the collection.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 30, 2008 9:08 pm 
Beginner
Beginner

Joined: Fri Sep 12, 2008 10:55 am
Posts: 33
well, I'm doing a:

Code:
List<Primary> primaries = criteria.list();

for( Primary p : primaries )
{
    List<Secondary> l = p.getSecondaries(); // no query showing up here in logging

    System.out.println( l.size() );  size is 1..when the db has 2

    for( Secondary s : l )
    {
        System.out.println( s.getStatusCode() );  // shows A, not A then I
    }
}



So it is only loading up my values based on my criteria, not on a after the fact filter. The select statement is limited, as is the data - which is what I would actually expect. It would be very poor performance in my mind if hibernate was getting ALL the rows - and then using some filter to limit the data I was requesting in my getSecondaries() call...at 1,000,000 transaction per day (made that up), that would be a whole lot of filtering :)

But it still doesn't answer my last question...the LEFT_JOIN works, but the INNER_JOIN does not - even tho a straight SQL query returns that same data (in this case)?


Top
 Profile  
 
 Post subject: List vs Set
PostPosted: Fri Oct 31, 2008 3:19 pm 
Newbie

Joined: Thu Feb 09, 2006 11:06 am
Posts: 2
Location: Boerne, TX
Have you tried changing the Primary so that it contains a List of Secondary instead of a Set?


Top
 Profile  
 
 Post subject:
PostPosted: Tue Nov 04, 2008 12:17 pm 
Beginner
Beginner

Joined: Fri Sep 12, 2008 10:55 am
Posts: 33
I have ended up making changes to the PK of these classes. The PK for both classes in just and identity column. Their Join is based on another field...

Now what happens is even more bothersome...

The first query is the left outer join query as shown in one of the above quotes. However, what ends up happening is what bothers me. Hibernate appears to go back to the Secondary class and re-runs an SQL for the Primary object (again). So if my initial query returns a result set that contains both the Primary and Secondary objects, the Primary object(s) appear to be loaded fully. Once the Secondary is started to load, once it hits the "ManyToOne" member for Primary, another query is fired. I really do not understand why hibernate is re-running another query here.

A) Shouldn't the object have already been loaded?
B) Why is LAZY loading not being honored for Secondary?


Top
 Profile  
 
 Post subject:
PostPosted: Tue Nov 04, 2008 3:02 pm 
Beginner
Beginner

Joined: Wed Sep 24, 2008 5:44 pm
Posts: 34
Ok I may be a little off on what you're asking here but here goes.

And for the record I'm not really sure what you're saying in the first sentence of your last post.

I feel like hibernate is behaving properly and am not sure what you're expecting it to do. Or maybe I'm reading this wrong...

You have set your Secondaries and lazily loaded so upon calling criteria.list() it will fire off a query to get the data to populate the primaries and the identifiers of the secondaries without retrieving their data.

Upon referencing each secondary [for (Secondary s : l)] hibernate will issue another query to your database to load that data since it hasn't been loaded yet.

I don't believe setting method (inner/outer) changes if the results are loaded lazily or not. However specifying the secondaries FetchMode.JOIN will tell hibernate to perform the join from primaries to secondaries in the initial query and load them both.

and as a p.s. inner joins and outer joins will return the same results if for every primary there exists a secondary.

hope I addressed some of your problems
cheers


Top
 Profile  
 
 Post subject:
PostPosted: Tue Nov 04, 2008 6:33 pm 
Beginner
Beginner

Joined: Fri Sep 12, 2008 10:55 am
Posts: 33
ok - let's start again then:

Tables:
Code:
CREATE TABLE `primary_table` (
  `primary_pk` int(11) NOT NULL auto_increment,
  `PRIMARY_ID` int(11) default NULL,
  PRIMARY KEY  (`primary_pk`),
  UNIQUE KEY `Index_2` USING BTREE (`PRIMARY_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

insert into `primary_table`(`primary_pk`,`PRIMARY_ID`) values (1,255);

CREATE TABLE `secondary_table` (
  `ID` int(11) NOT NULL auto_increment,
  `STATUS` varchar(255) default NULL,
  `primary_id` int(10) unsigned NOT NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `Index_2` (`primary_id`,`STATUS`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;


insert into `secondary_table`(`ID`,`STATUS`,`primary_id`) values (1,'A',255);
insert into `secondary_table`(`ID`,`STATUS`,`primary_id`) values (2,'C',255);
insert into `secondary_table`(`ID`,`STATUS`,`primary_id`) values (3,'I',255);
insert into `secondary_table`(`ID`,`STATUS`,`primary_id`) values (4,'P',255);


Mappings:
Code:
@Entity
@Table( name = "primary_table", catalog = "premierdb", uniqueConstraints = @UniqueConstraint( columnNames = "PRIMARY_ID" ) )
public class PrimaryTable implements java.io.Serializable
{

    // Fields   

    private Integer primaryPk;
    private Integer primaryId;

    private Set<SecondaryTable> secondaries;

    // Constructors

    /** default constructor */
    public PrimaryTable()
    {
    }

    /** full constructor */
    public PrimaryTable( Integer primaryId )
    {
        this.primaryId = primaryId;
    }

    // Property accessors
    @Id
    @GeneratedValue( strategy = IDENTITY )
    @Column( name = "primary_pk", unique = true, nullable = false )
    public Integer getPrimaryPk()
    {
        return this.primaryPk;
    }

    public void setPrimaryPk( Integer primaryPk )
    {
        this.primaryPk = primaryPk;
    }

    @Column( name = "PRIMARY_ID", unique = true )
    public Integer getPrimaryId()
    {
        return this.primaryId;
    }

    public void setPrimaryId( Integer primaryId )
    {
        this.primaryId = primaryId;
    }

    @OneToMany( cascade = CascadeType.ALL, mappedBy = "primary" )
    public Set<SecondaryTable> getSecondaries()
    {
        return secondaries;
    }

    public void setSecondaries( Set<SecondaryTable> secondaries )
    {
        this.secondaries = secondaries;
    }
}

@Entity
@Table( name = "secondary_table", catalog = "premierdb", uniqueConstraints = @UniqueConstraint( columnNames = {
    "primary_id",
    "STATUS" } ) )
public class SecondaryTable implements java.io.Serializable
{

    // Fields   

    private Integer id;
    private String status;
    private Integer primaryId;
    private PrimaryTable primary;

    // Constructors

    /** default constructor */
    public SecondaryTable()
    {
    }

    /** minimal constructor */
    public SecondaryTable( Integer primaryId )
    {
        this.primaryId = primaryId;
    }

    /** full constructor */
    public SecondaryTable( String status, Integer primaryId )
    {
        this.status = status;
        this.primaryId = primaryId;
    }

    // Property accessors
    @Id
    @GeneratedValue( strategy = IDENTITY )
    @Column( name = "ID", unique = true, nullable = false )
    public Integer getId()
    {
        return this.id;
    }

    public void setId( Integer id )
    {
        this.id = id;
    }

    @Column( name = "STATUS" )
    public String getStatus()
    {
        return this.status;
    }

    public void setStatus( String status )
    {
        this.status = status;
    }

    @Column( name = "primary_id", nullable = false )
    public Integer getPrimaryId()
    {
        return this.primaryId;
    }

    public void setPrimaryId( Integer primaryId )
    {
        this.primaryId = primaryId;
    }

    @ManyToOne( fetch = FetchType.LAZY )
    @JoinColumn( name = "PRIMARY_ID", referencedColumnName = "PRIMARY_ID", nullable = false, insertable = false, updatable = false )
    public PrimaryTable getPrimary()
    {
        return primary;
    }

    public void setPrimary( PrimaryTable primary )
    {
        this.primary = primary;
    }
}


Junit test:

Code:
@Test
    public void test1()
    {
        Criteria criteria =
            ProviderEntityManager.getEntityManager().getSession().createCriteria( PrimaryTable.class );

        criteria.add( Restrictions.eq( "primaryId", 255 ) );
       
        criteria.createCriteria( "secondaries", "secondary", CriteriaSpecification.LEFT_JOIN );

        criteria.add( Restrictions.eq( "secondary.status", "A" ) );

        List<PrimaryTable> pwns = criteria.list();
       
        System.out.println( "Found Primaries: " + pwns.size() );
       
        for( PrimaryTable p : pwns )
        {
            for( SecondaryTable s : p.getSecondaries() )
            {
                System.out.println( "\tStatus: " + s.getStatus() );
            }
        }
    }


This JUnit ends up giving 2 SQL's...

Join query
Quote:
select this_.primary_pk as primary1_93_1_, this_.PRIMARY_ID as PRIMARY2_93_1_, secondary1_.PRIMARY_ID as PRIMARY2_3_, secondary1_.ID as ID3_, secondary1_.ID as ID94_0_, secondary1_.PRIMARY_ID as PRIMARY2_94_0_, secondary1_.primary_id as primary2_94_0_, secondary1_.STATUS as STATUS94_0_ from premierdb.primary_table this_ left outer join premierdb.secondary_table secondary1_ on this_.PRIMARY_ID=secondary1_.PRIMARY_ID where this_.PRIMARY_ID=? and secondary1_.STATUS=?


and ..
query based off of SecondaryTable object creation creating another query to get the PrimaryTable object - this additional query makes ZERO sense considering the object was already gotten from the first query!

Quote:
select primarytab0_.primary_pk as primary1_93_0_, primarytab0_.PRIMARY_ID as PRIMARY2_93_0_ from premierdb.primary_table primarytab0_ where primarytab0_.PRIMARY_ID=?


output is:

Quote:
Found Primaries: 1
Status: A


Now, to add another twist: Changing SeconaryTable's getPrimary() method to to be annotated as:

Code:
@LazyCollection( LazyCollectionOption.TRUE )
@ManyToOne
@JoinColumn( name = "PRIMARY_ID", referencedColumnName = "PRIMARY_ID", nullable = false, insertable = false, updatable = false )
    public PrimaryTable getPrimary()


ONLY generates ONE query, and the output is:

Quote:
Found Primaries: 1
Status: A


Top
 Profile  
 
 Post subject:
PostPosted: Wed Nov 05, 2008 1:26 pm 
Beginner
Beginner

Joined: Fri Sep 12, 2008 10:55 am
Posts: 33
bad info on:
Code:
@LazyCollection( LazyCollectionOption.TRUE )
@ManyToOne
@JoinColumn( name = "PRIMARY_ID", referencedColumnName = "PRIMARY_ID", nullable = false, insertable = false, updatable = false )
    public PrimaryTable getPrimary()


LazyCollection doesn't do anything - accidentally left it on the wrong place..

However, leaving the ManyToOne as default of EAGER...

Does cause the query to do ANOTHER join to get the PrimaryTable for the Secondary object - does anyone else see a circular problem here but me?

This is what I'm after:
Code:
SELECT this_.primary_pk AS primary1_97_2_,
       this_.PRIMARY_ID AS PRIMARY2_97_2_,
       secondary1_.PRIMARY_ID AS PRIMARY2_4_,
       secondary1_.ID AS ID4_,
       secondary1_.ID AS ID98_0_,
       secondary1_.PRIMARY_ID AS PRIMARY2_98_0_,
       secondary1_.primary_id AS primary2_98_0_,
       secondary1_.STATUS AS STATUS98_0_,
       primarytab4_.primary_pk AS primary1_97_1_,
       primarytab4_.PRIMARY_ID AS PRIMARY2_97_1_
  FROM       premierdb.primary_table this_
          LEFT OUTER JOIN
             premierdb.secondary_table secondary1_
          ON this_.PRIMARY_ID = secondary1_.PRIMARY_ID
       LEFT OUTER JOIN
          premierdb.primary_table primarytab4_
       ON secondary1_.PRIMARY_ID = primarytab4_.PRIMARY_ID
WHERE this_.PRIMARY_ID = 255 AND secondary1_.STATUS = 'A'



What I'm really after is very simple..but maybe I've missed the boat with Hibernate Criteria, or it's just not capable of what I'm after:

Code:
SELECT this_.primary_pk AS primary1_97_2_,
       this_.PRIMARY_ID AS PRIMARY2_97_2_,
       secondary1_.PRIMARY_ID AS PRIMARY2_4_,
       secondary1_.STATUS AS STATUS98_0_
  FROM    premierdb.primary_table this_
       INNER JOIN
          premierdb.secondary_table secondary1_
       ON this_.PRIMARY_ID = secondary1_.PRIMARY_ID
WHERE this_.PRIMARY_ID = 255 AND secondary1_.STATUS = 'A'

However, I want PrimaryTable to actually have the SecondaryTable object loaded with the appropriate data as well, why would I want hibernate to do a LAZY or EAGER join when I know specifically what I want for the association...seems troublesome to have to write a specific HQL to handle this scenario....anyone?

oh - and you'll notice that the generated query on the dual outer joins - What's up with the multiple column names there?
ie: secondary1_.ID AS ID4_,
secondary1_.ID AS ID98_0_


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 14 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.