Thursday, 23 May 2013

LIFERAY:- Custom SQL Query from multiple tables

Introduction:-

Sometimes we encounter with a very complex DB and henceforth, a more complex query, where data has to ask many other tables for if they do have the same value with them or not. Basically, implementing a number of Constraints. For this we can then go for a CustomSQL mechanism of liferay.
For more:- liferay wiki

Problem:
Sometimes we want to extract data from two tables, but are not able to.

Solution:-

Lets assume we have a query like this:-
SELECT forumPosts.id as id, forumPosts.title as title, forumPosts.date as date, 
forumPosts.author as author, 
forumPosts.countAnswers as count_answers, forumUsers.firstName as fname, 
forumUsers.lastName as lname FROM forumPosts INNER JOIN users ON forumPosts.author = forumUsers.userid WHERE forumPosts.parentPost IS NULL ORDER BY id DESC;
 
Steps:
1. Create a portlet (as in here).
2. Create service.xml (as in here).
 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.1.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_6_1_0.dtd">
<service-builder package-path="com.test">
    <author>priyanka</author>
    <namespace>forum</namespace>


    <entity name="ForumPosts" table="forumPosts" local-service="true"
        remote-service="true" >

      <column name="id" type="long" primary="true" />
      <column name="title" type="String" />

      <column name="date" type="String" />
      <column name="parentPost" type="long" />
      <column name="author" type="long" />
      <column name="countAnswers" type="long" /> 
      <column name="lastPost" type="long" />
    </entity>

     <entity name="ForumUsers" table="forumUsers"
        local-service="true" remote-service="true">

        <column name="userid" type="long" primary="true" />
        <column name="firstName" type="String"  />
        <column name="lastName" type="String" />
    </entity>

</service-builder>

3. Do "ant build-service"
4. Create a folder custom-sql in {my-portlet}/docroot/WEB-INF/src/
5. Create default.xml in {my-portlet}/docroot/WEB-INF/src/custom-sql/
<?xml version="1.0" encoding="UTF-8"?>
<custom-sql>
    <sql file="custom-sql/get_forum_data.xml" />
</custom-sql>

6. Create get_forum_data.xml
<?xml version="1.0" encoding="UTF-8"?>
<custom-sql>
<sql id="com.test.service.persistence.ForumPostFinder.getForumData">
<![CDATA[

SELECT forumPosts.id as id, forumPosts.title as title, forumPosts.date as date, 
forumPosts.author as author, 
forumPosts.countAnswers as count_answers, forumUsers.firstName as fname, 
forumUsers.lastName as lname FROM forumPosts INNER JOIN users ON forumPosts.author = forumUsers.userid WHERE forumPosts.parentPost IS NULL ORDER BY id DESC;
 ]]>
</sql>
</custom-sql>


7. Create a class ForumPostFinderImpl under  package com.test.service.persistence

public class ForumPostFinderImpl extends BasePersistenceImpl implements
        ForumPostFinder {
public static String GET_FORUM_DATA =
ForumPostFinder.class.getName()+".getForumData";

    public Object
getForumData()
            throws SystemException {
        // open a new hibernate session in normal case when you are opening
        // session for same entity
        Session session = null;
        SQLQuery query = null;
        QueryPos qpos;
        try {
            session = openSession();
            String sql = CustomSQLUtil.get(
GET_FORUM_DATA);
            // create a SQLQuery object
            query = session.createSQLQuery(sql);
            query.setCacheable(false);


            query.addScalar("id", Type.INTEGER);
            query.addScalar("title", Type.STRING);
            query.addScalar("date", Type.DATE);
            query.addScalar("author", Type.STRING);
            query.addScalar("count_answers", Type.
INTEGER);
            query.addScalar("fname", Type.STRING);
            query.addScalar("lname", Type.STRING);
          

           //This is used when you want to pass any value to the query and place "?" in the query where you want the value to be set
           //qpos.add(Id);
           
        } catch (Exception e) {
            System.out.println("Exception : Finder custom");
            e.printStackTrace();
        } finally {
            closeSession(session);
        }

        // execute the query and return a list from the db
        return (Object) query.list().get(0);
    }
}
 


8. Go to ForumPostLocalServiceImpl and add the following functionpublic Object getForumPostAndUserData() throws SystemException {
        return ForumPostFinderUtil.getForumData;
    }


9. Again do ant build-service.

10. ForumPostFinderUtil and interface ForumPostFinder is created.

11. In your portlet doView()
                    ForumPostLocalServiceUtil
                                .getForumPostAndUserData());


This is all, you can build-service and then deploy the portlet.

Enjoy Coding :)

15 comments:

  1. thanks for this nice article, but one question is this working on liferay 6.0.6?

    ReplyDelete
  2. Although I haven't tried, but it should work with 6.x

    ReplyDelete
  3. Hi Priyanka,

    can you please elebroate doView()

    to get the records.

    ReplyDelete
    Replies
    1. for displaying a list of objects you have two options:-
      1. Convert it to JSON
      2. You must have a model class to map it.

      Delete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Hi Priyanka,

    I am using Liferay 6.1 with Tomcat 7 bundele.

    ForumPostFinderUtil and interface ForumPostFinder is not created for me.

    Can you explain what I have done wrong.

    ReplyDelete
    Replies
    1. Are you getting any error while performing build-service. if yes, please paste here, so we can figure out what's wrong?

      Delete
    2. I was getting the same problem, the problem was in the name of the FinderImpl class, it has to have the same name as one of the entities.

      For example:
      I have the entity Solution
      I wanted a custom query for retrieving the last solutions with some joins in the sql, so I created the class LastSolutionFinderImpl. The service builder didn't create any class.

      The problem was solved changing the name to SolutionFinderImpl, it seems that it only create the other classes when you are using the name of an entity in the name of the class.

      Bye and thanks for the tutorial :)

      Delete
    3. It is ForumPostsImpl not ForumPostImpl

      Delete
  6. I am getting error invalid column name .. can any one explain please

    ReplyDelete
  7. diacaeWces-mo_Tulsa Julie Ritter Here
    ringhandsoma

    ReplyDelete