13 Replies Latest reply on Nov 12, 2005 5:58 AM by marius.oancea

    Suggestion: Seam control over EJB3 Query objects?

    rdewell

      Does it make sense for Seam to have special handling capabilities / annotations for EBJ3 Query objects? I'm thinking of cases were I generate an EJB3 Query using information in a bean, return it from a getter on my bean, and would like to expose it as a DataModel. Further, I'd like to hand over control of "paging" functionality to Seam -- letting Seam hang on to the Query I generated for future changes to setFirstResult, setMaxResults.. Seam would manage those properties as I paged through a list of results in the view. In short my query becomes a "Seam managed Query".

      So, for example, in the HotelBookingAction. Instead of using getResultList(), which is problematic for large result sets, you could optionally just return the Query itself and let the paging properties of the Query be "managed" elsewhere.

      Kind of thinking out loud here. I know paging functionality, etc, is perfectly doable without Seam, but I'm just thinking that since Seam is already so closely related to EJB3...

      Ryan

        • 1. Re: Suggestion: Seam control over EJB3 Query objects?
          gavin.king

          Maybe. Add a request to JIRA and I'll give it some thought when I get a chance.

          • 2. Re: Suggestion: Seam control over EJB3 Query objects?

            I've been thinking the same thing would be quite useful. I've always been a big fan of ColdFusion and what it could do with direct SQL use within the page. With EJB 3 and Seam as a flexible 'layer', managed DataModels as a better alternative to RowSets or ResultSets is much cooler/efficient.

            • 3. Re: Suggestion: Seam control over EJB3 Query objects?
              werpu

              Well JSF would allow such an infrastructure... the Datamodel is the perfectly suited hook for such a thing...

              • 4. Re: Suggestion: Seam control over EJB3 Query objects?
                gavin.king

                So, I put some thought into this last night. The best I could really come up with was something like the following:


                @Name("bookingList")
                @NamedQuery(name="userBookingList",
                 ejbql="from Booking b where b.user = :user order by b.checkinDate")
                public class BookingListAction implements Serializable
                {
                
                 @In @Parameter
                 private User user;
                
                 @PageSize
                 private int pageSize = 100;
                 @Page
                 private int page = 0;
                
                 @Query(name="userBookingList",
                 context="bookingDatabase")
                 private Query query;
                
                 @DataModel
                 private List<Booking> bookings;
                 @DataModelSelection
                 private Booking booking;
                
                 @Factory("bookings")
                 public String execute()
                 {
                 bookings = query.getResultList();
                 return "bookings";
                 }
                
                 public String nextPage()
                 {
                 page++;
                 return null;
                 }
                
                 public String cancel()
                 booking.cancel();
                 return null;
                 }
                
                }



                Which I'm not really sure is sufficiently better than:

                @Name("bookingList")
                @NamedQuery(name="userBookingList",
                 ejbql="from Booking b where b.user = :user order by b.checkinDate")
                public class BookingListAction implements Serializable
                {
                
                 @In(create=true)
                 private EntityManager bookingDatabase;
                
                 @In
                 private User user;
                
                 private int pageSize = 100;
                 private int page = 0;
                
                 @DataModel
                 private List<Booking> bookings;
                 @DataModelSelection
                 private Booking booking;
                
                 @Factory("bookings")
                 public String execute()
                 {
                 bookings = bookingDatabase.createQuery("userBookingList")
                 .setParameter("user", user)
                 .setMaxResults(pageSize)
                 .setFirstResult(page*pageSize)
                 .getResultList();
                 return "bookings";
                 }
                
                 public String nextPage()
                 {
                 page++;
                 return null;
                 }
                
                 public String cancel()
                 booking.cancel();
                 return null;
                 }
                
                }


                What do you guys think?

                If anyone has better suggestions let me know. The only other thing I can really think of is to have a JSF component which just "does everything", which I think was more like what was envisaged in the original suggestion. But I'm not sure I want to throw things like that into the Seam core.

                I guess one day, it would make sense to grow an add-on component library as a seperate package. If there are volunteers to actually start development on such a sub-project, let me know...

                • 5. Re: Suggestion: Seam control over EJB3 Query objects?
                  gavin.king

                  Note that the relevant JIRA issue is here:

                  http://jira.jboss.com/jira/browse/JBSEAM-87

                  • 6. Re: Suggestion: Seam control over EJB3 Query objects?
                    gavin.king

                    I guess I could go for this if it was actually a way to really remove the Query instance from the code completely. But then how do I decide when is the correct moment to actually execute the query?

                    I guess you could try something like this:


                    @Name("bookingList")
                    @NamedQuery(name="userBookingList",
                     ejbql="from Booking b where b.user = :user order by b.checkinDate")
                    public class BookingListAction implements Serializable
                    {
                    
                     @In @Parameter
                     private User user;
                    
                     @PageSize
                     private int pageSize = 100;
                     @Page
                     private int page = 0;
                    
                     @QueryResult @DataModel
                     private List<Booking> bookings;
                     @DataModelSelection
                     private Booking booking;
                    
                     @Factory("bookings")
                     @ExecuteQuery(name="userBookingList",
                     context="bookingDatabase")
                     public String execute()
                     {
                     return "bookings";
                     }
                    
                     public String nextPage()
                     {
                     page++;
                     return null;
                     }
                    
                     public String cancel()
                     booking.cancel();
                     return null;
                     }
                    
                    }
                    
                    
                    But @ExecuteQuery does not seem like a very kosher use of annotations....
                    


                    • 7. Re: Suggestion: Seam control over EJB3 Query objects?
                      gavin.king

                      Hmmmmm ...... maybe something like this:


                      @Name("bookingList")
                      @NamedQuery(name="userBookingList",
                       ejbql="from Booking b where b.user = :user order by b.checkinDate")
                      @QueryFactory(variable="bookings",
                       query="userBookingList",
                       context="bookingDatabase")
                      public class BookingListAction implements Serializable
                      {
                      
                       @In @Parameter
                       private User user;
                      
                       @PageSize
                       private int pageSize = 100;
                       @Page
                       private int page = 0;
                      
                       @QueryResult @DataModel
                       private List<Booking> bookings;
                       @DataModelSelection
                       private Booking booking;
                      
                       public String nextPage()
                       {
                       page++;
                       return null;
                       }
                      
                       public String cancel()
                       booking.cancel();
                       return null;
                       }
                      
                      }
                      
                      


                      • 8. Re: Suggestion: Seam control over EJB3 Query objects?
                        rdewell

                        The last example you gave seems to be getting closer to the target... I'm concerned that relying on named queries may not be flexible enough. What about something like:

                        @QueryResult @DataModel
                        private List<Booking> bookings;
                        
                        @DataModelQuery
                        public Query createQueryPrototype(){
                         .. create the possibly dynamic query here from the session, filling it with info from the bean
                         ...
                         return query;
                        }
                        


                        Now, Seam needs to detect when: PageSize changes, current Page changes, other data in the model changes?

                        Then, it retrieves a new "query prototype", populates maxResults and first, executes, and populates "bookings" @DataModel/@QueryResult.


                        • 9. Re: Suggestion: Seam control over EJB3 Query objects?
                          rdewell

                          IMO, your last example with @QueryFactory looks pretty nice. Very terse. I like that Seam could populate my query bindings via @Parameter as well.

                          I still think there would need be an alternate way to allow for a method to return a possibly dynamically created Query as well.

                          Does / would @Parameter have a name attribute so that you could match it up to the named var in the query string without relying on the name of the private variable?

                          • 10. Re: Suggestion: Seam control over EJB3 Query objects?
                            gavin.king

                            OK, I'm converging on something like the following. Note that if we do this, it has to be able to work for both action-style event listeners and the factory stuff for GET requests.


                            This is "factory" style:


                            @NamedQuery(name="userBookingList",
                             ejbql="from Booking b where b.user.username = :user order by b.checkinDate")
                            
                            @Name("bookingList")
                            @Scope(CONVERSATION)
                            public class BookingListQuery implements Serializable
                            {
                            
                             @In @Parameter
                             User user;
                            
                             @QueryResult(query="userBookingList",
                             context="bookingDatabase")
                             @DataModel
                             private List<Booking> bookings;
                            
                             @DataModelSelection
                             private Booking booking;
                            
                             @PageSize private int pageSize = 100;
                             @Page private int page = 0;
                            
                             @Factory("bookings")
                             @Begin
                             public void begin() {}
                            
                             public String nextPage()
                             {
                             page++;
                             bookings = null;
                             return null;
                             }
                            
                             public String cancel()
                             booking.cancel();
                             return null;
                             }
                            
                            }




                            This is "event listener" style:


                            @NamedQuery(name="userBookingList",
                             ejbql="from Booking b where b.user = :user and b.checkinDate > :mindate order by b.checkinDate")
                            
                            @Name("bookingList")
                            @Scope(CONVERSATION)
                            public class BookingListQuery implements Serializable
                            {
                            
                             @In @Parameter
                             private User user;
                            
                             @Parameter
                             Date mindate;
                            
                             public void setMindate(Date mindate)
                             {
                             this.mindate = mindate;
                             }
                            
                             @QueryResult(query="userBookingList",
                             context="bookingDatabase")
                             @DataModel
                             private List<Booking> bookings;
                            
                             @DataModelSelection
                             private Booking booking;
                            
                             @PageSize private int pageSize = 100;
                             @Page private int page = 0;
                            
                             @Begin
                             public String begin()
                             {
                             return "bookings";
                             }
                            
                             public String nextPage()
                             {
                             page++;
                             bookings = null;
                             return null;
                             }
                            
                             public String cancel()
                             booking.cancel();
                             return null;
                             }
                            
                            }


                            The idea is that the query would be executed at the end of any method during the INVOKE_APPLICATION or RENDER_RESPONSE phases where the @QueryResult attribute is null.

                            Only problem with that idea is that you would not get to actually do anything with the query result list in the code. I'm trying to figure out if that is a showstopper or not....

                            • 11. Re: Suggestion: Seam control over EJB3 Query objects?
                              gavin.king

                               

                              "rdewell" wrote:
                              I still think there would need be an alternate way to allow for a method to return a possibly dynamically created Query as well.


                              Sure, that is a trivial extension. But I want to get it right for the static query case first.

                              "rdewell" wrote:
                              Does / would @Parameter have a name attribute so that you could match it up to the named var in the query string without relying on the name of the private variable?


                              Sure, of course.

                              • 12. Re: Suggestion: Seam control over EJB3 Query objects?
                                gavin.king

                                So, I implemented this stuff, but I don't think I'll commit it. I think it has a tendency to make code less readable rather than mode readable, and doesn't really reduce LOC.

                                Here is HotelBookingAction after migrating to this stuff:

                                @NamedQuery(name="findHotels",
                                queryString="from Hotel where lower(name) like :search or lower(city) like :search or lower(zip) like :search or lower(address) like :search")

                                @Stateful
                                @Name("hotelBooking")
                                @Interceptor(SeamInterceptor.class)
                                @Conversational(ifNotBegunOutcome="main")
                                @LoggedIn
                                public class HotelBookingAction implements HotelBooking, Serializable
                                {
                                private static final Logger log = Logger.getLogger(HotelBooking.class);

                                @PersistenceContext(type=EXTENDED)
                                private EntityManager em;

                                private String searchString;

                                @QueryParameter
                                private String search;

                                @QueryPageSize
                                private int pageSize = 50;

                                @QueryResult(namedQuery="findHotels",
                                persistenceUnit="bookingDatabase")
                                @DataModel
                                private List<Hotel> hotels;

                                @DataModelSelectionIndex
                                private int hotelIndex;

                                @Out(required=false)
                                private Hotel hotel;

                                @In(required=false)
                                @Out(required=false)
                                @Valid
                                private Booking booking;

                                @In
                                private User user;

                                @In
                                private transient FacesContext facesContext;

                                @In(required=false)
                                private BookingList bookingList;

                                @Begin
                                public String find()
                                {
                                hotel = null;
                                hotels = null;
                                search = searchString==null ? "%" : '%' + searchString.toLowerCase().replace('*', '%') + '%';
                                return "main";
                                }

                                public String getSearchString()
                                {
                                return searchString;
                                }

                                public void setSearchString(String searchString)
                                {
                                this.searchString = searchString;
                                }

                                public String selectHotel()
                                {
                                if ( hotels==null ) return "main";
                                setHotel();
                                return "selected";
                                }

                                public String nextHotel()
                                {
                                if ( hotelIndex<hotels.size()-1 )
                                {
                                ++hotelIndex;
                                setHotel();
                                }
                                return null;
                                }

                                public String lastHotel()
                                {
                                if (hotelIndex>0)
                                {
                                --hotelIndex;
                                setHotel();
                                }
                                return null;
                                }

                                private void setHotel()
                                {
                                hotel = hotels.get(hotelIndex);
                                log.info( hotelIndex + "=>" + hotel );
                                }

                                public String bookHotel()
                                {
                                if (hotel==null) return "main";
                                booking = new Booking(hotel, user);
                                booking.setCheckinDate( new Date() );
                                booking.setCheckoutDate( new Date() );
                                return "book";
                                }

                                @IfInvalid(outcome=REDISPLAY)
                                public String setBookingDetails()
                                {
                                if (booking==null || hotel==null) return "main";
                                if ( !booking.getCheckinDate().before( booking.getCheckoutDate() ) )
                                {
                                log.info("invalid booking dates");
                                FacesMessage facesMessage = new FacesMessage("Check out date must be later than check in date");
                                facesContext.addMessage(null, facesMessage);
                                return null;
                                }
                                else
                                {
                                log.info("valid booking");
                                return "success";
                                }
                                }

                                @End
                                public String confirm()
                                {
                                if (booking==null || hotel==null) return "main";
                                em.persist(booking);
                                if (bookingList!=null) bookingList.refresh();
                                log.info("booking confirmed");
                                return "confirmed";
                                }

                                @Destroy @Remove
                                public void destroy() {
                                log.info("destroyed");
                                }

                                }


                                I'm not seeing that this is an improvement. Rather, it's smearing out a well-defined concern and making it more difficult to see what is the flow of execution.

                                • 13. Re: Suggestion: Seam control over EJB3 Query objects?
                                  marius.oancea

                                  Hmm. Ok very nice idea but don't you think will be the same problem as with @DataModel ? (Only one per component).

                                  So I think:

                                  @Parameter, @Page, @PageSize has to know to wich query referes.