• Liferay Service Builder and Transaction

    Posted on February 6, 2015 by Hamidul Islam in Liferay.

    transaction-top


    Liferay Service Builder is one of the great tool to auto generate code to interact with database. But have you ever thought of transaction in Liferay Service Layer? In simple I am just reminding you about database roll back. In this post I will focus on the Transactional aspect of Liferay Service Layer.  

    NoteNoteA transaction is a set of tasks that you want to treat as a unit. The unit should be completed as a whole or not at all.


    Real-time Example:
    Say you want to transfer money on-line from your account to another account. The whole process can be considered as
    i) Check the sufficient balance.
    ii) Deduct specific amount from account.
    iii) Deposit the same amount to the destination account.

    In this case the whole process is a set of 3 tasks. If any one of these task fails then the whole process must be rolled back. For example consider the last task No. (iii) got failed. That means that amount is deducted from your account but its not deposited in the destination account. In this situation we must consider about transaction.


    I am assuming that you know the Liferay Service Builder tool. This post does not explain how to use to Liferay Service builder. 

    service.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.2.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_6_2_0.dtd">
    <service-builder package-path="com.proliferay.demo.sbuilder">
    	<author>Hamidul Islam</author>
    	<namespace>transaction</namespace>
    
    	<entity name="Customer" local-service="true" remote-service="true">
    		<column name="customerId" type="long" primary="true" />
    		<column name="firstName" type="String" />
    		<column name="middleName" type="String" />
    		<column name="lastName" type="String" />
    		<column name="age" type="int" />
    	</entity>
    
    	<entity name="Address" local-service="true" remote-service="true">
    		<column name="addressId" type="long" primary="true" />
    		<column name="customerId" type="long" primary="true" />
    		<column name="address" type="String" />
    		<column name="contactNo" type="int" />
    	</entity>
    </service-builder>
    

     In the above service.xml there are two entities. First entity is Customer and second entity is Address


    NoteNote: The services methods are executed in a transaction which means that if your database supports it, then if there is an error in the middle of the execution of a service method (or in any other method invoked by it), all the previous operations will be undone (rolled back) automatically for you. Liferay implements this under the hood using Spring's transactions mechanisms.


     

    Scenario 1

    We need add customer and their respective address. Both has to be inserted in different database tables. Consider every Customer should have valid address.  That means that after adding customer there must be a valid address record in address table. Therefore if we fail to add address then we must also should not add any customer without address. 

    Solution:

    Every liferay service methods are Transactional in nature. That means every local service impl methods are transnational. We can add records in both the tables from a single method by adding custom method in local service impl file. 

    	public void addCustomerAddress(Customer customer, Address address) throws SystemException{
    			/**
    			 * Inserting data to Customer
    			 */
    			long customerId = counterLocalService.increment(Customer.class.getName());
    			customer.setCustomerId(customerId);
    			customer = customerLocalService.addCustomer(customer);
    
    			/**
    			 * Inserting data to Address
    			 */
    			long addressId = counterLocalService.increment(Customer.class.getName());
    			address.setAddressId(addressId);
    			address.setCustomerId(customer.getCustomerId());
    			addressLocalService.addAddress(address);
    
    	}
    

     According to the above code after insertion of customer object if insertion of address object fails then whole process will be rolled back. Liferay handle this  Transaction without writing any custom code. Is not it fantastic? 

    Scenario 2

    Sometimes we want to roll back in some certain conditions. Say you are adding records in five tables one after another in a single method as shown in the above.

    Solution:

    If you want to roll back on the basis of certain conditions then we can explicitly throw any one of the below two exceptions

    i) com.liferay.portal.kernel.exception.PortalException

     Or

    ii) com.liferay.portal.kernel.exception.SystemException

    For example:

    throw new PortalException();
    

    The above code is sufficient to to roll back the previous insertions. 


    Good to know

    Internally Liferay services are annotated by @Transactional(isolation = Isolation.PORTAL, rollbackFor =  {PortalException.class, SystemException.class}) 

    This will be mentioned in your ${EntityName}LocalService interface. That means that service layer methods are Transactional. Roll back will be happened for the exceptions PortalException as well as SystemException. 


6 Responses so far.

  1. Iqbal Ahmed says:

    Really very nice article, explained in very simplified manner thanks for sharing 🙂

  2. Bhaskar Bhardwaj says:

    Great concept explained in simple way !

  3. Nagesh says:

    Hi,

    I liked your way of explanation. If possible try provide some examples in service builder like mappings 1-1, 1-M, M-M etc.

    I didn't found any examples like that any place properly.

    Thanks once again.

  4. pasha says:

    I am trying to implementing the Liferay Transaction RoleBack, I am not geting the deleted values(ie RollBack is not working).
    Please help.

    public void serveResource(ResourceRequest resourceRequest,
    ResourceResponse resourceResponse) throws IOException
    {
    try {
    deleteTheCheckedBoxVals(splitedVals);
    } catch (PortalException e) {
    _log.info("PortalException Exceptions in Main Method "+e);
    }
    }
    protected void deleteTheCheckedBoxVals(List splitedVals) throws PortalException{
    try {
    //Here I am deleting the each ID in DB, If Exection comes like NO ID FOUND IN DB, then I have to rollback the deleted ID
    for (String curDeltngIdVal : splitedVals) {
    XYZLocalServiceUtil.deleteAbc(Integer .parseInt(curDeltngIdVal));
    _log.info("deleted IDs are -->"+curDeltngIdVal);
    }
    } catch (NumberFormatException e) {
    _log.info("Number Formate Exceptions "+e);
    } catch (PortalException e) {
    _log.info("PortalException Exceptions "+e);
    throw new PortalException();
    } catch (SystemException e) {
    _log.info("SystemException Exceptions "+e);
    }
    }

  5. wzhonggo says:

    Is it possible add @Transactional in Liferay MVCPortlet?

    public class ProductPortlet extends MVCPortlet {
    @Transactional
    @ProcessAction(name = "addProduct")
    public void addProduct(ActionRequest actionRequest,
    ActionResponse actionRresponse) {

    try {
    AAALocalServiceUtil.aaa();
    BBBLocalServiceUtil.bbb();
    CCCLocalServiceUtil.ccc();
    } catch (Exception e) {
    e.printStackTrace();
    SessionMessages.add(actionRequest, "error");
    }
    }
    }

Top
%d bloggers like this:

By continuing to use the site, you agree to the use of cookies. more information

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close