In Salesforce Apex programming, a savepoint is a crucial feature that allows rolling back database changes to a specific point, ensuring data integrity. Previously, making external callouts after rolling back to a savepoint led to CalloutException, limiting developers. The Spring '24 release introduced `Database.releaseSavepoint()`, a significant enhancement in Apex. This method allows developers to release a savepoint after a rollback, enabling external callouts within the same transaction. This advancement greatly expands the capabilities of transaction control in Apex, allowing more sophisticated and integrated solutions with external services.
What is savepoint?
In Apex, a savepoint is a mechanism to define a point in your code where you can later roll back changes made to the database. Any DML statement that occurs after the savepoint can be discarded, restoring the database to its condition when you generate the savepoint.
This is particularly useful in scenarios where you need to ensure data consistency or handle exceptions gracefully.
What is releaseSavePoint?
The recent Salesforce Spring '24 release introduced a game-changing enhancement, addressing challenges associated with making callouts after rolling back uncommitted transactions in Apex code. Before this enhancement, attempting a callout after creating a savepoint within a transaction would result in a CalloutException, even if the transaction was rolled back. This limitation hindered developers from seamlessly integrating callouts with complex transactions involving savepoints.
The newly introduced Database.releaseSavepoint() method now empowers developers to explicitly release savepoints after rolling back changes. This capability allows subsequent callouts within the same transaction, unlocking critical use cases and enhancing the integration of external services and APIs with Apex transactions.
Exploring the Solution
To grasp the necessity of this enhancement, let's delve into the problem. The key issue arises when attempting an external callout after creating one or more save points. For instance, consider the following code snippet:
public with sharing class ReleaseSavePoint {
public static void calloutWithOutReleaseSavePoint(){
SavePoint point = Database.setSavePoint(); // setting a savepoint, so that if any error
//occur we can return back to this save point and all the database changes will be rollback
Account accountToInsert = new Account();
accountToInsert.name = 'Avenoir Technologies';
try{
insert accountToInsert;
}
catch(Exception error) {
makeCallOut();
}
}
public static void makeCallOut() {
try{
Http rest = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('callout:In_Info');
request.setMethod('GET');
HttpResponse response = new HttpResponse();
response = rest.Send(request);
if(response.getStatusCode() == 200) {
Map<String,object> data = (Map<String,object>)JSON.deserializeUntyped(response.getBody());
}
}
catch(Exception error) {
throw error;
}
}
}
When you call calloutWithOutReleaseSavePoint() this method you will get this
CalloutException ➖
This is because we are trying to make an external callout after savepoint. To address this issue, Salesforce introduced the Database.releaseSavepoint() method.
Here's how it solves the problem:
public with sharing class ReleaseSavePoint {
public static void calloutWithReleaseSavePoint(){
SavePoint point = Database.setSavePoint(); // setting a savepoint, so that if any error
//occur we can return back to this save point and all the database changes will be rollback
Account accountToInsert = new Account();
accountToInsert.name = 'Avenoir Technologies';
try{
insert accountToInsert;
}
catch(Exception error) {
Database.rollback(point);
Database.releaseSavepoint(point);
makeCallOut();
}
}
public static void makeCallOut() {
try{
Http rest = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://ipinfo.io/161.185.160.93/geo');
request.setMethod('GET');
HttpResponse response = new HttpResponse();
response = rest.Send(request);
if(response.getStatusCode() == 200) {
Map<String,object> data = (Map<String,object>)JSON.deserializeUntyped(response.getBody());
}
}
catch(Exception error) {
throw error;
}
}
}
In this method we are using the Database.releaseSavepoint() method before making callout that’s why we are not getting CalloutException.
Conclusion
The enhancement to Database.releaseSavepoint() in Spring '24 represents a significant advancement in transaction control flexibility within Apex. This enhancement allows for integrating external service callouts with intricate transaction savepoint logic that was previously unavailable. Developers now can seamlessly incorporate such use cases without encountering any limitations or exceptions.
Happy Coding! You can leave a comment to help me understand how the blog helped you. If you need further assistance, please contact us. You can click "Reach Us" on the website and share the issue with me.
Reference
Blog Credit:
S. Chaudhary
Salesforce Developer
Avenoir Technologies Pvt. Ltd.
Reach us: team@avenoir.ai
Are you in need of Salesforce Developers?
Reach Us Now!
The callout is never executed in your code