For out project we need to put users into correct public groups. This happens in a trigger when a user's contact is updated, depending on some of the fields there we decided where to put a user.
The problem is that sometimes we get this row locking error:
Failed to invoke future method 'public static void futurePutUserIntoCorrectPublicGroup(Set)' on class 'BP_User' for job id '707w000003xIXBI'
caused by: System.DmlException: Insert failed. First exception on row 0; first error: UNABLE_TO_LOCK_ROW, unable to obtain exclusive access to this record: []
This method(futurePutUserIntoCorrectPublicGroup()
) is just @future
annotated method that calls another method(putUserIntoCorrectPublicGroup()
) which does all the logic. Future here is needed to avoid MIXED_DML_OPERATION
error.
After some googling and cheatsheeting, we found out that insert of a GroupMember
record will lock Group table and the way to "fix" it is to query the locked records with FOR UPDATE
keywords.
However, this was not possible due to the compilation error:
Result: [OPERATION FAILED]: classes/BP_User.cls: SObject type does not allow locking rows (Line: 397, Column: 36)
So the question is: how can we handle UNABLE_TO_LOCK_ROW
errors on insert of a GroupMember
record?
Stripped code of this method if needed:
public static void putUserIntoCorrectPublicGroup(Set<Id> userIds) {
// Querying users, accountcontactroles and account
Map<Id, User> userMap; // = query
List<AccountContactRole> acrList; // = query
Map<Id, Account> dealerAccs; // = query
// Creating a list of public group names that we need
List<String> consShares = new List<String>();
// Querying all public groups with these names
// Can't use FOR UPDATE here, compilation error:
// Result: [OPERATION FAILED]: classes/BP_User.cls: SObject type does not allow locking rows (Line: 397, Column: 36)
List<Group> publicGroups = [SELECT Id, DeveloperName
FROM Group
WHERE DeveloperName IN :consShares];
List<GroupMember> gmList = new List<GroupMember>();
// Looping through the AccountContactRoles
for (AccountContactRole acr : acrList) {
// Getting the account and the user of this ACR from the maps
Account a;
User u;
// Finding needed public group
Group g;
// Create a new GroupMember object/record with GroupId being equal to the group we just found
// And UserOrGroupId being equal to the user which is linked via Contact to this ACR
GroupMember gm = new GroupMember(GroupId = g.Id, UserOrGroupId = u.Id);
gmList.add(gm);
}
// Deleting all old GroupMember records so that we only have up-to-date data
delete [SELECT Id FROM GroupMember WHERE UserOrGroupId IN :userIds];
// Inserting new GroupMember records
insert gmList;
}
I omitted most queries and loops to strip down the code and make it more readable. If we get this error, then it is on the very last line, where we insert that list.