Tuesday, 10 May 2016

Batch apex details and Examples

//Database is Class provided by Salesforce.
//Batchable is an global interface which is inside of the Database class.
//Batchable include 3 method prototypes: 1. start 2. execute 3. finish
//start and finish methods will execute only one time.
//execute method executes multiple times.
global class BatchUsage implements Database.Batchable <sobject>, Database.Stateful {
 //at a time we can inherit multiple interfaces but we cannot inherit multiple classes.
 //By default batch class is stateless (variable info. store in one method cannot be remembered in other method),
 //to make it stateful we should inherit Database.Stateful interface.
 String name = '';
 global Database.queryLocator start(Database.BatchableContext bc) {
 
  //From asynchronous to asynchronous we cannot call
  //(from batch class we cannot call future mehthod vice versa.).
  name += 'start';
  system.debug('@@@Start Method: '+name);
  //Collects the records to process. It will forward these records to execute method.
  //If we use Iterable<sobject> as return type for this start method it can hold
  //max. of 50k records only.
  //If we use Database.queryLocator as return type for this start method it can hold
  //max. of 50 million records.
  return Database.getQueryLocator('select id, name, Location__c from Department__c where Location__c = \'\'');
 }
 global void execute(Database.BatchableContext bc, LIST<sobject> sObjLst) {
  name += 'execute';
  system.debug('@@@Execute Method: '+name);
  //Split the records forwarded by start method into batches.
  //Default batch size is: 200.
  //Max. batch size is: 2000.
  List<department__c> depUPdLst = new List<department__c>();
  for(SObject sObj: sObjLst) {
   Department__c dept = (Department__c) sObj;//Type Casting.
   dept.Location__c = 'Bangalore';
   depUPdLst.add(dept);
  }
  if(depUPdLst.size() > 0)
   update depUPdLst;
 }
 global void finish(Database.BatchableContext bc) {
  name += 'finish';
  system.debug('@@@Finish Method: '+name);
  //Post Commit logic like sending emails for the success or failures of the batches.
  AsyncApexJob a = [select id, Status, NumberOfErrors, JobItemsProcessed,
  TotalJobItems, CreatedBy.Email from AsyncApexJob where id =: bc.getJobId()];
 
  Messaging.singleEmailMessage mail = new Messaging.singleEmailMessage();
  mail.setToAddresses(new String[]{a.CreatedBy.Email});
  mail.setSubject('Batch Class Result');
  mail.setHtmlBody('The batch Apex job processed ' + '<b>' + a.TotalJobItems + '</b>' +
  ' batches with '+ '<b>' + a.NumberOfErrors + '</b>' + ' failures.');
  //sendEmail methods
  Messaging.sendEmail(new Messaging.singleEmailMessage[]{mail});
 
  /*** Scheduling in minutes or hours ***/
  /*//Create object for schedulable class
  SchedulableUsage su = new SchedulableUsage();
  //Preparing chron_exp
  Datetime sysTime = System.now();
  sysTime = sysTime.addminutes(6);
  String chron_exp = '' + sysTime.second() + ' ' + sysTime.minute() + ' ' +
  sysTime.hour() + ' ' + sysTime.day() + ' ' + sysTime.month() + ' ? ' + sysTime.year();          
  System.schedule('Dep Update'+sysTime.getTime(),chron_exp, su);*/
 }
}
 </department__c></department__c></sobject></sobject></sobject>
=============================================================================================
To schedule the batch class we should write a separate class by implementing Schedulable interface.

After writing the above mentioned class to schedule navigate to: Develop> Apex Classes> Schedule Apex.

By clicking on Schedule Apex button we can schedule the batch class through user interface.

Note: Through user interface we cannot schedule in hours/minutes.

Schedulable Class Syntax:


global class SchedulableUsage implements Schedulable {
 global void execute(SchedulableContext sc) {
  BatchUsage bu = new BatchUsage();
  //Database.executeBatch(bu);//Default Batch Size is: 200.
  Database.executeBatch(bu,1500);//Max. Batch Size is: 2000.
 }
}

====================================================
 How to schedule batch apex in minutes/hours?
To schedule the batch class in minutes/hours, in the finish method we should use System.schedule method which will take 3 parameters Job Name, Chrone Expression and schedulable class instance name respectively.

    /*** Scheduling in minutes or hours ***/
//Create object for schedulable class
SchedulableUsage su = new SchedulableUsage();
//Preparing chron_exp
Datetime sysTime = System.now();
sysTime = sysTime.addminutes(6);
String chron_exp = '' + sysTime.second() + ' ' + sysTime.minute() + ' ' +
sysTime.hour() + ' ' + sysTime.day() + ' ' + sysTime.month() + ' ? ' + sysTime.year();          
System.schedule('Dep Update'+sysTime.getTime(),chron_exp, su);

==================================
==
>Is it possible to call batch class from one more batch class?
Yes it is possible, starting with Apex saved using Salesforce API version 26.0, you can call Database.executeBatch or System.scheduleBatch from the finish method. This enables you to start or schedule a new batch job when the current batch job finishes.

For previous versions, you can’t call Database.executeBatch or System.scheduleBatch from any batch Apex method. Note that the version used is the version of the running batch class that starts or schedules another batch job. If the finish method in the running batch class calls a method in a helper class to start the batch job,
the Salesforce API version of the helper class doesn’t matter.

=========================================================================
What is MIXED-DML-OPERATION error and how to avoid?

If we perform DML operation on standard/custom object and global objects(User, UserRole, Group, GroupMember, Permission Set, etc...)
in same transaction this error will come.

To avoid this error, we should perform DML operation on standard/custom object records in a different transaction.

In general all the apex classes and apex triggers execute synchronously (execute immediately).

if we perform DML operation on standard/custom object records asynchronously (execute in future context), we can avoid MIXED-DML-OPERATION error.

To execute logic asynchronously keep the logic in an apex method (in a separate apex class,
 not in same apex trigger) which is decorated with @future annotation.

see the below example -

public class TriggerUtility {
 /*
 1. Following future method execute asynchronously (whenever server is free it will execute in future context).
 2. We should not declare @future method in Apex Trigger.
 3. @future method should be always static.
 4. @future method accepts only primitive data types (Integer, String, Boolean, Date, etc...) as parameters and it won't accept
 non-primitive data types (sObject,Custom Objects and standard Objects etc.. ) as parameters.
 5. @future method should not contain return type. Always it should be void.
 6. From an apex trigger we can make only make asynchronous call outs. To make call out we should include "callout = true" beside the future @annotation.
 7. We cannot perform synchronous call outs from Apex Trigger.
 */
 //Below is the example for the future method -
 @future(callout = true)
 public static void processAsync(primitive parameters) {
  //Logic to insert/update the standard/custom object.
 }
}
======================================
What is the purpose of system.runAs()?
By default test class runs in System Mode. If you want to execute a piece of code in a certain user context then we can use system.runAs(UserInstance).
 For more details refer 2nd question in visualforce category.

To avoid MIXED-DML-OPERATION error we can include DML statements inside of system.runAs(),
 still the error persists keep DML statements inside of Test.startTest() and Test.stopTest().


 ======================================
  What is View State?
Creation: In a visualforce page, whenever we use form tag then view state will be created for the page.
Purpose: Assume that, we need to display the user input form in 3 different visualforce pages. In first and second pages,
user has to fill the information and click on Next button. On the final page, after filling the form if he/she will click on Save button,
 which ever the information we filled in the first and second pages should be also saved.
 View state will store the information which is filled in first and second page . To maintain the state of the page, we need view state.
Size: All the variables which we use in controller/extensions classes and expressions which are declared on the page will occupy the space in view state.
Governor Limits: Maximum size of the View State is: 135 KB.
Page Performance:
Whatever the variables information we don't required to maintain the state while navigating to other pages those variables
 we can decorate with transient keyword which won't occupy space in the view state.
Static variables also won't occupy space in the view state.
It is recommended to use only one form tag. If there are multiple form tags hierarchy of the folder structure increases which will occupy more space.


=========================================================

===================================================================
global class FieldsValidator implements Database.Batchable<Sobject> {
    global Database.queryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator('select name, fields_info__c, fields_count__c from ObjectsExistingFieldsInfo__c');
    }
    global void execute(Database.BatchableContext bc, List<SObject> sobjLst) {
        String[] types = new String[]{'Account','Contact'};
        String emailBody = '';
        // Make the describe call
        Schema.DescribeSobjectResult[] results = Schema.describeSObjects(types);
        Map<String,List<Field>> objFieldsDesMap = new Map<String,List<Field>>();
        // For each returned result, get some info
        for(Schema.DescribeSobjectResult res : results) {
            Map<String, Schema.SObjectField> objectFields = res.fields.getMap();
            for(Schema.SObjectField fDes : objectFields.values()) {
                if(fDes.getDescribe().isCustom()) {
                    system.debug(fDes.getDescribe().getName()+'@@@'+fDes.getDescribe().getType());
                    String fieldAPIName = String.valueOf(fDes.getDescribe().getName());
                    String fieldDataType = String.valueOf(fDes.getDescribe().getType());
                    if(objFieldsDesMap.containsKey(res.getName())) {
                        objFieldsDesMap.get(res.getName()).add(new Field(fieldAPIName,fieldDataType));
                    }
                    else {
                        objFieldsDesMap.put(res.getName(),new List<Field>{new Field(fieldAPIName,fieldDataType)});
                    }
                }    
            }
        }
        Map<String, ObjectsExistingFieldsInfo__c> objectInfoMap = new Map<String, ObjectsExistingFieldsInfo__c>();
        for(ObjectsExistingFieldsInfo__c objInfo : [select Field_API_Name__c , Fields_Count__c, Fields_Info__c from
        ObjectsExistingFieldsInfo__c where Field_API_Name__c in: types]) {
            objectInfoMap.put(objInfo.Field_API_Name__c,objInfo);
        }
        for(String type : types) {
            if(objectInfoMap.get(type).fields_count__c == null) {
                objectInfoMap.get(type).fields_count__c = objFieldsDesMap.get(type).size();
                objectInfoMap.get(type).fields_info__c = JSON.serialize(objFieldsDesMap.get(type));
            }
            else {
                System.debug('entering to else condition...');
                System.debug('entering to for loop..');
                List<Field> exstingFields = new List<Field>();
                List<Field> newFields = new List<Field>();
                Map<String,Field> bkupexstingFields = new Map<String,Field>();
                Map<String,Field> bkupnewFields = new Map<String,Field>();
                exstingFields = (List<Field>)JSON.deserialize(objectInfoMap.get(type).fields_info__c, List<Field>.class);
                System.debug('exstingFields: '+exstingFields);
                newFields.addAll(objFieldsDesMap.get(type));
                System.debug('objFieldsDesMap: '+objFieldsDesMap);
                System.debug('newFields: '+newFields);
                for(Field f : exstingFields) {
                    bkupexstingFields.put(f.apiName,f);
                }
                for(Field f : newFields) {
                    bkupnewFields.put(f.apiName,f);
                }
                for(Field f : exstingFields) {
                    bkupnewFields.remove(f.apiName);
                }
                for(Field f : newFields) {
                    bkupexstingFields.remove(f.apiName);
                }
                System.debug(bkupnewFields+'@@@'+bkupexstingFields);
                emailBody += type + ' : ';
                //for new fields
                for(Field f :bkupnewFields.values()) {
                    emailBody += f.apiName + ' with datatype '+f.dataType+' is newly created.';
                }
                objectInfoMap.get(type).fields_count__c = objFieldsDesMap.get(type).size();
                objectInfoMap.get(type).fields_info__c = JSON.serialize(objFieldsDesMap.get(type));
            }
        }
        /*** Sending Email ***/
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setToAddresses(new List<String>{system.label.Dev_Admin});
        mail.setSubject('Fields Update');
        mail.setPlainTextBody(emailBody);
        update objectInfoMap.values();
        Messaging.sendEmail(new Messaging.SingleEmailMessage[]{mail});
    }
    global void finish(Database.BatchableContext bc) {
    }
    public class Field {
        String apiName, dataType;
        public Field(String apiName, String dataType) {
            this.apiName = apiName;
            this.dataType = dataType;
        }
    }
}

=================================
Post a Comment