Salesforce Platform Developer I：Apex の基本とデータベース

# Apex の基本とデータベース

https://trailhead.salesforce.com/ja/content/learn/modules/apex_database

## Apex入門
### Apexとは
 Apex は、Java に似た構文を使用し、データベースのストアドプロシージャのように動作するプログラミング言語です。開発者は Apex を使用して、ボタンのクリックや関連レコードの更新、Visualforce ページなどのシステムイベントにロジックを追加することができます。

 #### 特徴
・ホスティング — Apex は、サーバー (Lightning Platform) 上で保存、コンパイル、実行されます。
・オブジェクト指向 — Apex は、クラス、インターフェース、継承をサポートします。
・強い型付け — Apex は、コンパイル時にオブジェクトへの参照を検証します。
・マルチテナント型 — Apex はマルチテナントプラットフォームで実行されるため、コードが共有リソースを独占しないような制限を適用して、回避コードから保護します。
・データベースとの統合 — レコードに簡単にアクセスして操作できます。Apex では、レコードとその項目に直接アクセスできます。また、それらのレコードを操作するためのステートメントとクエリ言語が提供されます。
・データ指向 — Apex では、データベースへのトランザクションアクセスが提供されるため、ロールバック操作が可能です。
・使いやすい — Apex は、使い慣れた Java イディオムに基づいています。
・テストが容易 — Apex には、単体テストの作成、実行、およびコードカバー率のサポートが組み込まれています。Salesforce では、プラットフォームのアップグレードの前にすべての単体テストを実行することによって、すべてのカスタム Apex コードが期待どおりに動作することを確認しています。
・バージョン管理 — カスタム Apex コードは、異なるバージョンの API に対して保存できます。


#### Apex言語の特長

他のオブジェクト指向プログラミング言語と同様に、Apex では次のような言語構成要素がサポートされます。
・クラス、インターフェース、プロパティ、コレクション (配列を含む)
・オブジェクトおよび配列表記
・式、変数、定数
・条件付きステートメント (if-then-else) とフロー制御ステートメント (for ループと while ループ)

他のオブジェクト指向プログラミング言語とは異なり、Apex では次の機能がサポートされます。
・クラウド開発 (Apex はクラウドで保管、コンパイル、および実行されるため)
・トリガー (データベースシステムのトリガーと類似)
・直接データベースコールが可能なデータベースステートメントと、データのクエリと検索を行うためのクエリ言語
・トランザクションとロールバック
・global アクセス修飾子 (public 修飾子よりも権限が高く、名前空間とアプリケーションの全体でアクセスが可能)
・カスタムコードのバージョン管理

<font color="red">さらに、Apex では大文字と小文字が区別されません。</font>
#### 開発ツール
・VSCode
・開発者コンソール

#### データ型
・標準データ型：Integer、Double、Long、Date、Datetime、String、ID、Booleanなど
・SObject（標準オブジェクト：Accountなど、カスタムオブジェクト：MyCustomObject__c など）
・List、Set、Map
```List<String> colors = new List<String>();```また```String[] colors = new List<String>();```
・該当Apex以外のクラス
　①自分作成クラス
　②Salesforce提供のクラス
　　例：Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();

##### CHALLENGE
```StringArrayTest.cls
public class StringArrayTest {

    public static List<String> generateStringArray(Integer n){
        List<String> returnList = new List<String>();
        for (Integer i=0;i<n;i++){
            returnList.add('Test '+i);
        }
        return returnList;
    }
}
```
## sObjectを使用する
### sObject を使用する
Salesforceのすべてのレコードは、ApexのsObjectとしてネイティブに表現されます。
各Salesforceレコードは、Salesforceに挿入される前はsObjectとして表されます。同様に、Salesforceから保持されているレコードが取得されると、sObject変数として保存されます。
Salesforceの標準およびカスタムオブジェクトレコードは、Apex ではそのsObject型に対応付けられます。
次の例では、Acme という名前で<font color="red">Account型のsObject</font>を作成して、acct変数に割り当てます。

```apex
Account acct = new Account(Name='Acme', Phone='(415)555-1212', NumberOfEmployees=100);
```
また
```apex
Account acct = new Account();
acct.Name = 'Acme';
acct.Phone = '(415)555-1212';
acct.NumberOfEmployees = 100;
```

### 汎用sObjectデータ型を操作する
sObject を操作する場合には、標準オブジェクトの Accountやカスタムオブジェクトの Book__cなど、特定のsObjectデータ型を使用します。
ただし、<font color="red">メソッドで処理する sObjectのデータ型が不明な場合は、汎用sObjectデータ型を使用できます。</font>

<font color="red">汎用sObjectデータ型で宣言された変数は、</font>標準オブジェクトレコードかカスタムオブジェクトレコードを問わず、<font color="red">任意のSalesforceレコードを参照できます。</font>
### CHALLENGE
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/424429/f28c8ee6-ea7f-7db5-9f9b-54f9dcec4baf.png" width=50%>

## DMLを使用してレコードを操作する
### DML操作
DMLでは、シンプルなステートメントを使用してレコードの挿入、更新、マージ、削除、復元を行うことで、簡単にレコードを管理できます。
最初に取引先sObjectが作成され、引数としてinsertステートメントに渡されます。これにより、レコードがSalesforce内に保持されます。
例
```apex
Account acct = new Account(Name='Acme', Phone='(415)555-1212', NumberOfEmployees=100);
insert acct;
```
### DML ステートメント
・insert　　　挿入
・update　　 更新
・upsert　　　※
・delete　　　削除
・undelete　　削除取消
・merge　　　 同じ sObject データ型の最大 3 つのレコードを 1 つのレコードにマージし、他のレコードを削除してから、関連レコードを再ペアレント化します。

#### ・新規レコードのsalesforce IDについて
SF内に自動設定
#### ・一括DML
List<sObject>作成、リストにsObjectをadd、その後はinsert、update該当リスト
```apex
List<Contact> conList = new List<Contact> {
        new Contact(FirstName='Joe',LastName='Smith',Department='Finance'),
        new Contact(FirstName='Kathy',LastName='Smith',Department='Technology'),
        new Contact(FirstName='Caroline',LastName='Roth',Department='Finance'),
        new Contact(FirstName='Kim',LastName='Shain',Department='Education')};
// Bulk insert all contacts with one DML call
insert conList;
```
#### ・upsert文法
upsert sObjectまたlist<sObject> [ID、idLookup項目、または外部ID]

##### 　　処理方式
　　・キーが一致しない場合、新規オブジェクトレコードが作成されます。
　　・キーが一度だけ一致したら、既存のオブジェクトレコードが更新されます。
　　・キーが複数回一致する場合は、エラーが生成され、オブジェクトレコードは挿入も更新もされません。

#### ・delete
レコードを削除できます。レコードを削除してもLightningプラットフォームから完全に削除されるわけではなく、復元できるように15日間はごみ箱に置かれます。

※復元方法はundelete SalesforceID

#### ・異常処理
DML操作が失敗した場合、DmlException型の例外が返されます。
```apex
try {
    // DML処理
} catch (DmlException e) {
    //異常発生の場合、処理方法、ログ出力など
}
```
#### データベースメソッドとDMLステートメント
##### ・DMLステートメント
　　```insert <List<sObject>>```
##### ・データベースメソッド
　　```Database.insert(List<sObject>,一括コミット要否(true,false))```

<font color="red">※区別：データベースメソッドは一部のレコードでエラーが発生した場合、成功したレコードはコミットされ、失敗したレコードについてはエラーが返されます。（バッチ処理時よく使います）</font>

##### ・処理結果判断
　　実行結果をDatabase.SaveResult[]に保存して、ループで各レコードの実行結果判断
```apex
Database.SaveResult[] srList = Database.insert(conList, false);
// Iterate through each returned result
for (Database.SaveResult sr : srList) {
    if (sr.isSuccess()) {
        // Operation was successful, so get the ID of the record that was processed
        System.debug('Successfully inserted contact. Contact ID: ' + sr.getId());
    } else {
        // Operation failed, so get all errors
        for(Database.Error err : sr.getErrors()) {
            System.debug('The following error has occurred.');
            System.debug(err.getStatusCode() + ': ' + err.getMessage());
            System.debug('Contact fields that affected this error: ' + err.getFields());
	 }
    }
}
```
##### ・DML ステートメントとデータベースメソッドの使い分け
DML 一括処理中に発生するエラーを、コントロールフローをその場で中断する Apex 例外として処理する場合、DML ステートメントを使用します。ここでは try . .catch ブロック) を使用します。この動作は、ほとんどのデータベース手続き型言語での例外の処理方法に似ています。

DML 一括操作の部分的な完了を可能にする場合は、Database クラスメソッドを使用します。レコードが失敗した場合でも、DML 操作の残りは終了できます。アプリケーションは拒否されたレコードを確認でき、可能であれば操作を再試行します。この形式を使用すると、DML 例外エラーが発生することがないコードを書くことができます。エラーが発生しない代わりに、作成したコードでは、成功または失敗を判断するための適切な結果配列を使用できます。Database クラスメソッドには、DML ステートメントに類似する、発生した例外をサポートする構文も含まれます。
#### 関連レコード処理
##### 追加
```Apex
Account acct = new Account(Name='SFDC Account');
insert acct;
ID acctID = acct.ID;
Contact mario = new Contact(
    FirstName='Mario',
    LastName='Ruiz',
    Phone='415.555.1212',
    AccountId=acctID);
insert mario;
```
##### 更新
```apex
Contact queriedContact = [SELECT Account.Name 
                          FROM Contact 
                          WHERE FirstName = 'Mario' AND LastName='Ruiz'
                          LIMIT 1];
queriedContact.Phone = '(415)555-1213';
queriedContact.Account.Industry = 'Technology';
update queriedContact;
update queriedContact.Account; 
```

##### 削除
親オブジェクトを削除すると、各子レコードが削除可能な場合は自動的に削除されます。
```apex
Account[] queriedAccounts = [SELECT Id FROM Account WHERE Name='SFDC Account'];
delete queriedAccounts;
```

#### トランザクションについて
DML 操作はトランザクション内で実行されます。トランザクションの実行には、すべての DML 操作が正常に完了することが求められます。いずれかの操作でエラーが発生した場合はトランザクション全体がロールバックされます。

#### CHALLENGE
```AccountHandler.cls
public with sharing class AccountHandler {
    public static Account insertNewAccount(String strname){
        try{
            Account acc = new Account(Name=strname);
            insert acc;
            return acc;
        }catch(DmlException e){
            return null;
        }
    }
}
```
## SOQLクエリを作成する
### SOQLとは
 Salesforce Object Query Language (SOQL) 
### APEXにSOQLでレコード取得
Account[] accts = <font color="red">[</font>SELECT Name,Phone FROM Account<font color="red">]</font>;
### 基本SOQL文法
```SELECT fields FROM ObjectName [WHERE Condition] [ORDER BY field ASC] [LIMIT 件数]```

### SOQLクエリの変数にアクセスする
```apex
String targetDepartment = 'Wingo';
Contact[] techContacts = [SELECT FirstName,LastName 
                          FROM Contact WHERE Department=:targetDepartment];
```
### 関連レコードを照会する
#### 親から子取得
・サブSELECTで検索
```apex
Account[] acctsWithContacts = [SELECT Name, (SELECT LastName FROM Contacts) FROM Account WHERE Name = 'SFDC Computing'];
Contact[] cts = acctsWithContacts[0].Contacts;
```
#### 子から親項目取得
```apex
Contact[] cts = [SELECT Account.Name FROM Contact];
Contact carol = cts[0];
String acctName = carol.Account.Name;
```

### SOQL forループを使用してレコードを一括で照会する
・ガバナ制限回避可能
```apex
for (Account[] tmp : [SELECT Id FROM Account]) {
    //処理
}
```

### CHALLENGE
<font color="red">※Contactのトリガーを無効する必要
```ContactSearch.cls
public class ContactSearch {
    public static List<Contact> searchForContacts(String lastname,String postcode){
        Contact[] cts = [SELECT Id,Name FROM Contact WHERE LastName=:lastname and MailingPostalCode=:postcode];
        return cts;
    }
}
```
## SOSLクエリの作成
### SOSLとは
Salesforce Object Search Language (SOSL)
### SOSL文法
```Apex
FIND 'SearchQuery' [IN SearchGroup] [RETURNING ObjectsAndFields]
```
#### パラメータ説明
・SearchQuery：AND、OR利用可能、*途中または末尾にある0個以上の文字と一致します。?ワイルドカードは検索語の途中または末尾にある1文字のみと一致します。大文字と小文字を区別しません。
・SearchGroup：省略可能です。ALL FIELDS（デフォルト）、NAME FIELDS、EMAIL FIELDS、PHONE FIELDS、SIDEBAR FIELDS
|SearchGroup|説明|
|:--|:--|
|ALL FIELDS|	検索可能なすべての項目を検索します。IN 句が指定されていない場合、ALL FIELDS がデフォルト設定です。|
|EMAIL FIELDS|	メール項目のみを検索します。|
|NAME FIELDS|	標準オブジェクトの名前項目のみを検索します。カスタムオブジェクトでは、「Name Field」として定義された項目が検索されます。標準およびカスタムオブジェクトでは、名前項目は nameField プロパティが true に設定されています。(詳細は、DescribeSObjectResult の fields パラメータの Field 配列を参照)。|
|PHONE FIELDS|	電話番号項目のみを検索します。
|SIDEBAR FIELDS|	サイドバーのドロップダウンリストに表示される有効なレコードを検索します。アプリケーションでの検索とは異なり、アスタリスク (*) ワイルドカードは検索文字列の最後に追加されません。|

・ObjectsAndFields：省略可能です。指定しないすると、全オブジェクト検索。

例
```
FIND {避難 OR *学} IN ALL FIELDS RETURNING Account(Name, BillingCity WHERE Industry='Apparel' ORDER BY Name DESC LIMIT 1)
```
### SOQLとSOSLの相違点と類似点
SOQLとSOSLは、2つの別個の言語であり、構文が異なります。各言語で使用事例も異なります。
下記の場合、SOQLを使用します。
　・1つのオブジェクトまたは互いに関連付けられている複数のオブジェクトからデータを取得する。
　・指定した条件を満たすレコード数を数える。
　・クエリの一部として結果を並び替える。
　・数値、日付、またはチェックボックス項目からデータを取得する。

下記の場合、SOSLを使用します。SOSLクエリは、オブジェクトのほとんどのテキスト項目を検索できます。
　・項目内に存在することがわかっている、特定の語のデータを取得する。SOSL は 1 つの項目内の複数の語をトークン化してそこから検索インデックスを構築できるので、SOSL 検索ではより高速でより多くの関連する結果を返すことができます。
　・相互に関連している、または関連していない複数のオブジェクトおよび項目を効率的に取得する。
　・ディビジョン機能を使用して、組織の特定のディビジョンのデータを最も効率的な方法で取得する。

### ApexにSOSL利用と開発者コンソールで [Query Editor (クエリエディター)] にSOSL利用区別
**<font color="red">クエリエディターと API の検索クエリは、中括弧で囲む必要があります ({Wingo})。これに対し、Apex では、検索クエリは単一引用符で囲みます ('Wingo')。</font>** 

#### Apex
```List<List<SObject>> searchList = [FIND 'Wingo' IN ALL FIELDS RETURNING Account(Name), Contact(FirstName,LastName)];```
#### Query Editor
```FIND {Wingo} IN ALL FIELDS RETURNING Account(Name), Contact(FirstName,LastName)```

### Apex利用方法
```apex
String soslFindClause = 'Wingo OR SFDC';
List<List<sObject>> searchList = [FIND :soslFindClause IN ALL FIELDS
                    RETURNING Account(Name),Contact(FirstName,LastName,Department)];
Account[] searchAccounts = (Account[])searchList[0];
Contact[] searchContacts = (Contact[])searchList[1];
System.debug('Found the following accounts.');
for (Account a : searchAccounts) {
    System.debug(a.Name);
}
System.debug('Found the following contacts.');
for (Contact c : searchContacts) {
    System.debug(c.LastName + ', ' + c.FirstName);
}
```
### CHALLENGE
#### 事前準備
１．開発者コンソールの [Debug (デバッグ)] メニューから [Execute Anonymous (匿名実行)] ウィンドウを開きます。
２．ウィンドウに次のスニペットを入力し、[Execute (実行)] をクリックします。
```
Account acct = new Account(
    Name='SFDC Computing',
    Phone='(415)555-1212',
    NumberOfEmployees=50,
    BillingCity='San Francisco');
insert acct;
// Once the account is inserted, the sObject will be 
// populated with an ID.
// Get this ID.
ID acctID = acct.ID;
// Add a contact to this account.
Contact con = new Contact(
    FirstName='test1',
    LastName='Smith',
    Phone='(415)555-1212',
    Department='Wingo',
    AccountId=acctID);
insert con;
Lead led = new Lead(
    FirstName='test2',
    LastName='Smith',
Company='test');

insert led;
```
#### ソース
```ContactAndLeadSearch.cls
public class ContactAndLeadSearch {
    public static List<List<sObject>> searchContactsAndLeads(String name){
        List<List<sObject>> findName = [FIND :name IN ALL FIELDS RETURNING contact(FirstName,LastName),Lead(FirstName,LastName)];
        return findName;
    }
}
```