プログラマ38の日記

主にプログラムメモです。

Salesforce: Apex開発で気をつけていること

自分がApexでの開発で気をつけていることのメモです。

  1. Salesforce IDの項目はID型で定義する。
  2. トリガでの他オブジェクトの作成/更新処理を作り過ぎない
  3. 項目自動更新とトリガを混在させない
  4. そこでしか使わない処理は無名ブロックで囲み変数のスコープを限定する
  5. insert upsert update delete を try catch で制御するならロールバックは忘れずに行う
  6. トリガのテンプレートは必ず使う

1. Salesforce IDの項目はID型で定義する。

ID型とString型は互換性があり意識しなくても問題はないのですが、ID型で定義することで、後でプログラムを読む際にSalesforce IDが入る項目ということは明確になります。(特に関数の引数をID型で定義するとわかりやすくなります)
Stringで定義すると、Salesforce IDなのかそれとも別のユニークキーなのか明確にならないので、複数人数で開発する際に認識が違ってしまうこともあります。

 

気をつけたいのがID型で定義すると、文字列がIDとして正しくないとエラーになってしまいますが、後でエラーになるよりは最初でエラーとなった方が苦労しなくてすむと思います。


2. トリガでの他オブジェクトの作成/更新処理を作り過ぎない

トリガで、ある条件によって他のオブジェクトの作成/更新を行いすぎると、後々ガバナ制限の問題がでたり、データパッチを当てるときに注意が必要になったりします。
(気をつけて作成/更新をする分にはいいのですが、知らなくて勝手に他のオブジェクトが作成されたり、更新されたりすることで障害になったりするのが問題です)
利用者の理解が得られるなら、項目の作成/更新時に自動で動くのではなく、項目を更新後にボタンをクリックしてもらいボタンの処理として実装するのが安全だと考えています。
※もちろん時と場合によるので、その時その時で判断は必要ですが。


3. 項目自動更新とトリガを混在させない

トリガの作成が見えているオブジェクトならば、項目自動更新は設定せずに、トリガの中にロジックを全て持っていくのが良いと考えています。
というのは、新規作成時でも、項目自動更新で updateも走るので、トランザクションの中の処理量が増え、ガバナ制限や、性能への影響が出てくる可能性があります。
また、項目自動更新だと実行される順番が制御できないので、トリガの方が実装が簡単な場合があります。

 

4. そこでしか使わない処理は無名ブロックで囲み変数のスコープを限定する

一時的な変数を使って制御をしたい時があります。例えば次のようなコードがあります。

    List<Case> caselist = Trigger.New;
    Map<Id, Contact> contactMap = new Map<Id, Contact>();
    Set<Id> contactset = new Set<Id>();
    for(Case c : caselist){
      if( c.ContactId != null ){
        contactset.add(c.ContactId);
      }
    }
    contactMap = new MapMap<Id, Contact>([Select Id,Name,Email From  Contact where Id =:contactset]);

上記の「contactset」は「contactMap」を作成するためだけの一時的なSetで、後続では使わないものとします。
その場合、次のように無名ブロックで囲むのはどうでしょうか。

    List<Case> caselist = Trigger.New;
    Map<Id, Contact> contactMap = new Map<Id, Contact>();
    {
        Set<Id> contactset = new Set<Id>();
        for(Case c : caselist){
          if( c.ContactId != null ){
            contactset.add(c.ContactId);
          }
        }
        contactMap = new MapMap<Id, Contact>([Select Id,Name,Email From  Contact where Id =:contactset]);
    }

こうすることで、処理で使いたい変数と、一時的な変数のインデントが分かれるのと、ブロック内で変数のスコープが
限定されるので、後続の処理でうっかり「contactset」を使ってしまうことがなくなります。


5. insert upsert update delete を try catch で制御するならロールバックは忘れずに行う

処理の中で、2つ以上のオブジェクトに対してレコードを作成/更新することはよくありますが、その処理を
try catchで囲ってしまうと、最初の作成/更新は正常で、次の作成/更新でエラーの場合、最初の作成/更新が実行されてしまいます。そのため、きちんとcatchの中でロールバックする処理を忘れないようにします。
(結構よく忘れてしまうため備忘としてアウトプットしておこうと思います)

 

6. トリガのテンプレートは必ず使う

ちょっとしたトリガだとトリガ内にロジック書きたくなるのですが、一律テンプレートを使うことで統一した方が後々楽になります。

[トリガのテンプレートサンプル]

trigger SampleTrigger on Sample__c (
     before insert
    ,before update
//  ,before delete
    ,after insert
    ,after update
//  ,after delete
//  ,after undelete
){
    
    if( Trigger.isInsert ){

        if(Trigger.isBefore ){
            SampleTriggerHandler.OnBeforeInsert(Trigger.new);
        }
        
        if (Trigger.isUpdate) {
            SampleTriggerHandler.OnBeforeUpdate(Trigger.new, Trigger.old);
        } 
        
        //if (Trigger.isDelete) {
        //  SampleTriggerHandler.OnBeforeDelete(Trigger.old);
        //}
    
    } else if( Trigger.isAfter ){

        if(Trigger.isBefore ){
            SampleTriggerHandler.OnAfterInsert(Trigger.new);
        }
        
        if (Trigger.isUpdate) {
            SampleTriggerHandler.OnAfterUpdate(Trigger.new, Trigger.old);
        }
        
        //if (Trigger.isDelete) {
        //  SampleTriggerHandler.OnAfterDelete(Trigger.old);
        //}
        
        //if (Trigger.isUnDelete ){
        //  SampleTriggerHandler.OnUndelete(Trigger.new);
        //}
    }
}

1つつのオブジェクトに1つのトリガが推奨されていますが、1つのトリガに1つのハンドラクラスである必要はありません。例えば、トリガのロジックが膨大で複数人で開発する際には、最初に人数分のハンドラクラスを作れば効率はあがります。

 たくさんクラスを作ることでSOQLの発行回数が気になる場合は、static変数を使ってSOQLの発行回数を下げるやり方が効果的です。(開発を始める前にstatic変数でキャッシュするレコードを決め、あらかじめstaticメソッドを準備しておけば開発は楽になります)

 

static変数を使う方法は次の記事に書いています。

crmprogrammer38.hatenablog.com

 

Salesforce: Force.com移行ツール(Force.com Migration Tool)で指定するコンポーネント名をselect可能なSオブジェクト

件名がやたら長くなってしまいました。

オブジェクトのメタデータは、オブジェクトの情報をまとめて取得する方法と、カスタム項目、リストビュー、レコードタイプ、入力規則、カスタムボタンまたはカスタムリンクを個別で取得する方法があります。
個別で取得する際にコンポーネント名を指定しますが、コンポーネント名は対応するSオブジェクトから取得できるものがあります。

 

まず Force.com移行ツール(Force.com Migration Tool)での指定の方法は以下の通りです。

オブジェクトの情報を全て取得する

Force.com移行ツール(Force.com Migration Tool)で、オブジェクトのメタデータを取得する際にはpackage.xmlに次のように指定します。

    <types>
        <members>Sample__c</members>
        <name>CustomObject</name>
    </types>

 上記で指定すると、オブジェクトの情報が全て取得できます。

 

カスタム項目、リストビュー、レコードタイプ、入力規則、カスタムボタンまたはカスタムリンクを個別に取得する

個別に取得する際にはpackage.xmlにそれぞれ次のように指定します。

 

カスタム項目

    <types>
        <members>Sample__c.SampleField__c</members>
        <name>CustomField</name>
    </types>

リストビュー

    <types>
        <members>Sample__c.sampleview1</members>
        <name>ListView</name>
    </types>

レコードタイプ

    <types>
        <members>Sample__c.rectype1</members>
        <name>RecordType</name>
    </types>

入力規則

    <types>
        <members>Sample__c.rule1</members>
        <name>ValidationRule</name>
    </types>

カスタムボタンまたはカスタムリンク

    <types>
        <members>Sample__c.button1</members>
        <name>WebLink</name>
    </types>

上記のように個別に指定することにより必要なものだけ取得することができます。

 

個別に指定する際のコンポーネント名をSオブジェクトから取得する

Force.com移行ツール(Force.com Migration Tool)のpackage.xmlに記載するコンポーネント名は次のSオブジェクトから取得できます。一部取得できないコンポーネント名もあります。

ラベル 対応するSObject コメント
カスタム項目 項目定義(FieldDefinition) △次のselectで取得できますが、項目レベルセキュリティが外れていると結果に含まれません。

Select EntityDefinition.QualifiedApiName
 ,QualifiedApiName
From
  FieldDefinition
where EntityDefinition.QualifiedApiName = 'オブジェクトAPI'
リストビュー リストビュー(ListView) ○次のselectで取得できます。(ビューで「自分にのみ表示」にすると結果に含まれませんが、そもそも「自分にのみ表示」のビューはメタデータが取得できないので関係ありません)
Select 
 SobjectType
 ,DeveloperName
From
  ListView
レコードタイプ レコードタイプ(RecordType) ○書くまでもないと思います。
Select 
  SobjectType
 ,DeveloperName
From
  RecordType
入力規則 ×調べたのですがありませんでした。画面からコピーするしかないのかなと思います。
ボタンまたはリンク カスタムボタンまたはカスタムリンク(WebLink) ○次のselectで取得できます。(今回は関係ありませんが、WebLinkのURL項目にはJavaScriptも書いてあるので、ロジックを調査する時は便利です)
Select
  PageOrSobjectType
 ,Name
From
  WebLink

※SオブジェクトはAPIバージョン40で確認しました。古いデータローダでは取得できない可能性があります。 

最後に

環境間の移送は変更セットが便利なのですが、標準項目が選択できないので、標準項目とレコードタイプの組み合わせや、標準項目とプロファイルの組み合わせなどを移送したい時はForce.com移行ツール(Force.com Migration Tool)を使うと便利です。

package.xmlを書くのが多少しんどいですが。

 

Salesforce: テストクラスの作成での留意事項

SalesforceのApex開発を行う場合、本番環境にリリースするためにテストクラスを作成する必要があります。

 

設定とちょっとのトリガなどの開発であれば、テストクラスの作成はさほど大変ではないのですが、サイトやコミュニティで多くの画面を作りこむ場合、テストクラスの作成に多くの労力を必要とします。

さらに、モジュールのリリースの際のテストクラス実行中の待ち時間も意外とかかるのでテストクラスの作成の仕方は工夫が必要だと感じています。

 

個人的にテストクラスの作成で思うことは次になります。

  1. テストクラスでは最小メソッド単位の呼び出しをメインとする
  2. テストデータ登録時のトリガは極力スキップする仕組みを作る

1. テストクラスでは最小メソッド単位の呼び出しをメインとする。

テストクラスは大体以下のようになると思います。

  • カスタムWebServiceの場合メソッドを実行
  • トリガの場合、あらかじめ必要なマスタを用意した後、対象のオブジェクトへレコードを作成、更新、削除を実行
  • コントローラの場合、Visualforceページで必要なパラメータを指定後、コントローラのメソッドを実行

上記のテストメソッドは必要ですが、全てのテストメソッドを上記の書き方で書く必要はないと考えています。(労力もかかりますし、テストメソッドの実行時間も長くなります)

通常、上記のメソッドの中はさらに細かいメソッドで構成されています。テストクラスでは、その細かいメソッド単位で実行するようにします。

細かいメソッドは、そのクラス内でしかアクセスしないためprivate や protected としていると思いますが、Salesforceではテストクラスからのテスト用に テストビジブルアノテーション(@TestVisible)が用意されているので、これを使っていきます。

 

テストクラスでカバレージを上げるポイントとして、通常クラスの細かいメソッド内で業務ロジックを記載する場合、SOQLを発行しないようにしておきます。(SOQLを発行するメソッドはSOQLの発行だけの機能とします)

 

コントローラクラスの作成例は次の通りです。

public class SampleController {

    public Case caseObj {get; set;}
    String caseId;
    
    public APC_CUS_200_ShinchokuDetailCtrl(){
      caseId =  ApexPages.currentPage().getParameters().get('caseId');
    }
    
    //vf action
    public void doinit(){
      caseObj = [Select Id  ,Type ,Origin ,Subject ,Priority ,ParentId ,AccountId
                 From Case where id = :caseId ]; 
    }

    public PageReference doSomething() {
      Account acc        = getAccount();
      Case    parentcase = getParentCase();
      
      doCheck(acc, parentcase);
    }
    
    private Account getAccount(){
      if( caseObj.AccountId == nul ){
        return null;
      } else {
          return [Select  Id ,Name ,Industry 
From Account where ID =:caseObj.AccountId][0];
      }
    }

    private Case getParentCase(){
      if( caseObj.ParentId == nul ){
        return null;
      } else {
          return [Select  Id  ,Type ,Origin ,Subject ,Priority 
From Case where ID =:caseObj.ParentId][0];
      }
    }
    
    @TestVisible
    private void doCheck(Account acc,Case parentcase){
    
      if( caseObj.Origin != parentcase.Origin ){
        ・・・・・
      }
    
      if( acc.Industry == 'Education' && parentcase.Origin == 'Phone' ){
        ・・・・・
      }
    }
}

そして上記のdoCheckのメソッドのテストクラスは次のようにします。

@isTest
private class SampleControllerTest {

  static testMethod void testMethod01() {
    SampleController ctrl = new SampleController();
    
    Case caseobj    = new Case();
    Case parentcase = new Case();
    Account acc     = new Account();
//必要なマスタの値のセットをする。
    
    ctrl.caseObj = caseobj;
    ctrl.doCheck(acc, parentcase);
  }
}

こうすることで、特にマスタを事前に登録しておくことも不要となり、doCheckの業務ロジックを確実にテストすることができ、カバレージも確保できます。

 

カスタムWebサービスや、トリガでも同様に、カスタムWebサービスのクラスやトリガハンドラークラスの細かいメソッドを呼び出すことで対応します。

2. テストデータ登録時のトリガは極力スキップする仕組みを作る

上記1ではデータを作成せずにテストをすることで、効率をあげるということを書いていますが、テストクラスでテストデータの作成は避けて通ることができません。

ですが、テストクラス用のデータを作成するときにそのオブジェクトのトリガが邪魔になるときがあります。
そのトリガ処理のテストをしたいわけではないのに、トリガのエラーを解決するために時間がかかったりして効率が落ちます。

そこで、データ登録時のトリガをスキップする処理を用意しておくのはどうでしょうか。そこまで大した変更をせずにテストで使いたいデータを登録ができて、本来のテストに集中することができます。(もちろん全てのオブジェクトのトリガにスキップを入れる必要はありません、効果的な箇所だけ対応するのが良いと思います)
トリガのスキップについては以前に書いていますのでそちらを参照ください。

Salesforce: カスタムボタンのJavaScriptで注意すること

Salesforceでは、標準レイアウトにカスタムボタンを配置してアプリケーションを作成していきます。(Lightning Experience ではなくClassicを対象とした場合です)

 

自分がカスタムボタンの作成時に注意することのメモです。

  1. 環境依存の文字列はカスタム表示ラベルを使う
  2. オブジェクトの値を差し込む際は、改行や「"」や「'」が入る文字列は使わない
  3. カスタムWebServiceの戻り値ではJSON文字列を返却する
  4. 制御が複雑で、ネストが深くなる場合にはtry, catchをうまく使う

1. 環境依存の文字列はカスタム表示ラベルを使う

カスタムボタンでカスタム表示ラベル( {!$Label.Label1} など)は使えるので、環境依存する文字列、例えば、接続先のURLや、項目IDなどはカスタム表示ラベルにしておきます。※でも、項目IDについては、項目定義(FieldDefinition)のデュラブル ID(DurableId)から取得することをお奨めします。

2. オブジェクトの値を差し込む際は、改行や「"」や「'」が入る文字列は使わない

下記のようなId、日付項目、数値、チェックボックスは問題ないのですが、

( {!Opportunity.Id}、{!Opportunity.CloseDate}、{!Opportunity.Amount}、{!Opportunity.IsClosed} )
次のように、値に改行がはいったり、シングルクオテーション、ダブルクオテーションがはいる可能性がある項目は差し込むとJavaScriptがエラーとなる場合があります。
( {!Opportunity.Description} )

APIコール数が増えますが、カスタムWebServiceをコールしてApex内で処理するようにします。

3. カスタムWebServiceの戻り値ではJSON文字列を返却する

カスタムWebServiceでチェックをしたり、処理結果を戻したりする際には、JSON文字列で返却すると便利です。次のようなクラスを用意しておいて

public class ReturnSample {
  public Boolean IsSuccess;
  public Boolean IsWarning;
  public Boolean IsError;
  public String Message;
  public List<String> successIdList;
  public List<String> warningIdList;
  public List<String> errorIdList;
}

カスタムWebServiceで、上記インスタンスシリアライズして返却します。

ReturnSample retobj = new ReturnSample();
retobj.IsSuccess = true;
retobj.IsWarning = true;
retobj.IsError   = false;
retobj.Message   = '処理は完了しましたが、警告があります。XXXX';
retobj.successIdList = successlist;
retobj.warningIdList = null;
retobj.errorIdList   = warninglist;

return JSON.serialize(retobj);

そして、カスタムボタンではJSON文字列をデシリアライズして使います。

var retjson = sforce.apex.execute('SampleWebService','samplemethod', { arg1 : '{!Case.Id}' } ); 
var retobj  = JSON.parse(retjson);

多少込み入った処理を実装する場合でも、必要な情報は返却できると思います。

4. 制御が複雑で、ネストが深くなる場合にはtry, catchをうまく使う

カスタムボタンである程度ロジックを組む場合があると思います。 特にやっかいなのが、確認メッセージを出して"OK"なら次へ、"キャンセル"の場合は中断するという制御です。

この制御をifを使って実装すると、ネストが確認メッセージ分深くなっていきます。そこで、次のようにtry, catchを使ってジャンプさせてしまうのはどうでしょうか。


try{
  var retjson = sforce.apex.execute('SampleWebService','samplemethod', 
{ arg1 : '{!Case.Id}' } ); 
  var retobj  = JSON.parse(retjson);

  if( retobj.IsSuccess == true ) {
    if( retobj.IsWarning == true ){
      if( confirm('次の警告が発生しています。処理を続けますか? ' + retobj.Message) == false){
         throw { quietclose : true };
      }

      var retjson2 = sforce.apex.execute('SampleWebService','samplemethod2', 
{ arg1 : retobj.successIdList ,arg2 : retobj.warningIdList } ); 
      var retobj2  = JSON.parse(retjson2);

      if( retobj2.IsSuccess == true ){
        if( confirm('次の警告が発生しています。処理を続けますか? ' + retobj2.Message) == false){
           throw { quietclose : true };
        }

        var retjson3 = sforce.apex.execute('SampleWebService','samplemethod3',    { arg1 : retobj2.successIdList ,arg2 : retobj2.warningIdList } ); 
        var retobj3  = JSON.parse(retjson2);
        ・・・・・・・・・

      } else {
        throw retobj2.Message;
      }

    }
  } else {
    throw retobj.Message;
  }

} catch( ex ){
  if( ex.quietclose == true ){
    //何もせず終了する
  } else {
    alert( ex );
  }
}

ポイントは、メッセージ表示はcatchにまかせている点、catchにquietcloseプロパティをtrueにして投げると、何も表示せずに終わるようにしている点です。

上記のサンプルでも結構わかりずらいですが、これを全てifのブロックで実現した場合、ネストが相当深くなりメンテナンスが大変になっていきます。

 

最後に

カスタムボタンや、メールテンプレートではカスタム表示ラベルが使えるのに、差込項目として選べないんですよね。
本番にリリースするたびに、カスタムボタンなメールテンプレートの文字列を本番用に変更しているプロジェクトがあると大変そうだなと思います。カスタム表示ラベルではなく直に環境依存の文字列を書いていると予想できますが、一度決めた運用なので変更しずらいのだと思います。

 

後、この慣れ親しんだカスタムボタンもLightning Experienceを採用して使わなくなる日も近いのでしょうね。。 ガバナ制限を回避のため、カスタムボタンの中でカスタムWebServiceを細かく呼び出すような設計をすることがありますが、そういった部分がLightning Experienceでどうなるのか把握できていません。なかなかLightning Experienceへ切り替えができないでいます。。

100記事書いてみて思ったこと

今回がちょうど101回目になります。

 

100回書いてみて思ったことと、これからやってみたいことを書いてみようと思います。

 

[思ったこと]

文章を書くのが下手

プログラマとしてドキュメントを書くことは多く、今までそれなりに書いてきましたが、あらためて文章が下手だなーと感じました。

書く内容も細かかったり、場合分けが多かったりもするのですが、今読み返すと文章の展開がおかしく「え?」というものが多いです。

書いているときは、書きたい内容はあるのですが、うまく文章にできていないんだと思います。もっと書けば、経験値が貯まりコツがつかめてくるんじゃないかと期待しています。他にも文章を書くのが上手な方を真似してみようと思います。

 

1つの記事を書くのにそれなりの時間はかかる

ブログなので、ちょっとした合間にできるものなのかなと最初は思っていたのですが、自分には全くそんなことはありませんでした。

プログラムに関することでは、サンプルプログラムを載せることがありますが、サンプルプログラムでもきちんと動くものを載せたいので、ある程度のテストを行ったりしています。
また、最終的に書く文章は下手なんですが、下手なりに色々試行錯誤してみたりして時間がかかります。テストデータとか表で表現しようものならtableタグを書いたりするのに多くの時間がかかりました。(tableタグを書くのがあまりに辛くExcelマクロを作りました(笑))

世の中には、すごい人たちがいてとても込み入った内容でも、わかりやすく表現しているので、素直に尊敬します。

 

楽しい

なんでもそうですが、1つ1つ作り上げていく作業はとても楽しくて好きです。これまでは自分でプログラムを作って、自分だけで楽しんでいたのですが、ブログ上でインターネットに公開していくことができます。今までかいた100記事の中でいくつかプログラムを公開してみたのですが、これからも公開していきたいなーと思いました。

今は100ですが、500、1000と書いていけたらとても素敵なものになると思います。(私の場合何年かかるかわかりませんが・・)

でも、内容はSalesforce関連が多くなってきています。やはりJavaSQLといったプログラム言語ではなくて、アプリケーションになっているので気づくことが多いのだと思います。今は流れにまかせて書きたいなと思ったことを書こうと思います。

 

[これからやってみたいこと]

今まで書いたものを修正する

てにをはがそもそもおかしいものや、伝えたいことが伝わらないような文章になっているもの、説明が不足しているものを直していきたいなーと思っています。

たまにタグがおかしくて、ちょっとフォントのサイズが大きい箇所があったりするのも直そうと思います。
あと、せっかく使っているので、少しずつでもはてなブログの機能を習得していきたいです。プログラムコードとかももっと綺麗に表示できる仕組みに変更しようと思います。
ちょっとずつ覚えて、覚えたものを使ってみるぐらいでやってみようと思います。(自分の性格は、大変不便なのですが、"勉強する" に対してモチベーションが全くあがらなくなります。。)

はてなProに入ったり、他のWebサービスを使ってみる

はてなProに変更してみたいです。ただちょっとお金がかかるんですよね。しばらくはこのまま無料で使えるバージョンでやっていこうと思います。
後、他のWebサービスなども使ってみたいと思います。その道のスペシャリストの方のメルマガなども購読したいなーと思っています。

なかなか、今まで自分と関りがなかったものに手を出してこなかったのですが、情報を発信することの楽しさや難しさがわかり、がぜん興味が湧いています。

 

最後に

このブログを見てくれている方、スターをつけてくれる方、ありがとうございます。

内容は自分でいうのもなんですが相当偏っていると思います。これからも、他の方と極力かぶらない内容で書いていこうと思います。書いた内容で少しでも参考になることがあれば嬉しいです。

雑記/Java: 最近のJavaで辛いなと思ったこと

自分は他の言語はあまりやらないので、Javaプログラムがメイン言語となっています。

なので、何か作ろうと思ったらコマンドラインのプログラムもJavaGUIのプログラムもJavaで済まそうとしてしまいます。

 

言語の拡張や新しい仕組みの導入などは無条件に受け入れる(しかない)のですが、最近のJava(Java8)で辛いなと思うことがあります。

 

  1. サイズがでかい(どんどんでかくなる)
  2. sun.jdbc.odbc.JdbcOdbcDriverが削除された
  3. (その他)javaとは無関係ですが、apache poiでExcelを扱いづらい

 

1. サイズがでかい(どんどんでかくなる)

Javaで作ったツール類を公開したいと思ったときに、jreが大きすぎてjreを含めてツールを公開するのをためらってしまいます。(java8だと200MB近くになっています。)
持ち歩きが不便です。
JavaFXが組み込まれてから一気にサイズが大きくなった気がしていますが、JavaFXが標準で必要な人の割合の方が少ないと思うんですよねー。以前のように別のライブラリにしとけばいいのにと思っています。

※個人的にJava Swingをよく使うのですが、Java Swingが廃止されてJavaFXを使わないといけない日が来たら、別の言語に乗り換えるタイミングかなと思っています。

 

2. sun.jdbc.odbc.JdbcOdbcDriverが削除された

MS-ACCESSや、ODBC Text Driver、ODBC Excel Driverを使ってるので、なくなると本当に困ります。(Java6やjava7をまだ使っています)

Java8でsun.jdbc.odbc.JdbcOdbcDriveを使うやり方を書いてくれている方がいて神だと思いました。(※)

個人的な思いとしてJavaDBはいらないので、MS-ACCESS使わせて欲しいです。そもそもDerby使うならhsqldbとかh2 databaseとか使いますし。

 

※Java9では、このやり方は使えなくなると思います。jdk9で試した結果、sun.security.*パッケージのアクセスができずjava.lang.NoClassDefFoundErrorとなりました。
今のところこのエラーを解消できておらず絶望的な感じです。

 

3. (その他)javaとは無関係ですが、apache poiでExcelを扱いづらい

完全に、javaと関係ないです。apache poiを使うと、xlsxのファイルを読むと滅茶苦茶メモリ使うんですね。xlsxが圧縮されているんだと思いますが、元のファイルサイズの10倍近くメモリを要求してくるイメージです。poiだと数式の評価がめんどくさいのと、他のブックを参照している数式は正しく値が取得できませんでした。

JExcelApiならもうちょっと改善されてるんですかね。。使ってみようと思います。

 

最後に

なんだかんだでJavaが好きなんですよね。Eclipseが偉大なのと、ライブラリも充実してますし。

言語拡張はそこまでいらないです。JavaのStreamAPIとかラムダ式とか使いやすいと思いませんでした。(頭が古いだけかもしれません)

 

そろそろJava9を使い始めてみようと思います。

CSV: DBクライアント ExecuteQuery でH2 Databaseを使う

前回CSVの加工の方法の1つとして紹介した「H2 Database」を使う方法です。

前提として、「Execute Query」と「H2 Database」のモジュールはダウンロードしていることとします。

 

次の内容です。

  1. 「Execute Query」を起動する。
  2. JDBCドライバを設定する。
  3. 接続先を登録する。
  4. 実際に使ってみる。

1. 「Execute Query」を起動する。

ダウンロードしたモジュールに、exeファイルが用意されていて、「eq.exe」をダブルクリックすると起動します。この場合、32bitのjavaコマンドを探しにいくので、64bitで動かしたい場合は、コマンドで起動します。

"D:¥x64Java¥bin¥javaw.exe" -Xms256m -Xmx2G -jar eq.jar

 「-server」オプションなど入れると気持ち早くなるかもしれません。

 2. JDBCドライバを設定する。

起動したらJDBCドライバを登録します。「Drivers > New JDBC Driver」をクリック

f:id:crmprogrammer38:20170907091259p:plain
Database Driverで、次のように設定 PathにはH2 Databaseのjarを指定します。
f:id:crmprogrammer38:20170907091604p:plain

3. 接続先を登録する。

「Connections> New Connection」をクリック

f:id:crmprogrammer38:20170907092032p:plain
「Database Connection」で次の登録をしてConnectボタンをクリックするとスタンドアロンで起動します。(urlをjdbc:h2:mem:. としたので、メモリモードです)

f:id:crmprogrammer38:20170907092211p:plain

4. 実際に使ってみる。

まず「H2 Database」の良さは、テーブル定義で桁が不要です。

create table sample( textfield varchar, decimalfield decimal)

こんなテーブル定義がOKです。桁を指定しないと最大サイズで定義されます。

そして、前回紹介した csvread と csvwrite でCSVを扱っていきます。

 

例として、Salesforceのグループメンバー(GroupMember)のデータを加工します。結局Salesforceを例にするのが簡単でした。。。

グループメンバー(GroupMember)はグループ:GroupId(公開グループ、キュー)に、ユーザかグループ:UserOrGroupId(公開グループ、ロール、ユーザ)が割り当てられている情報になります。

SalesforceのSOQLでは、ユーザかグループ:UserOrGroupIdの情報は参照先名がないので、SOQLでは辿ることができません。

こんな時はCSVを使って加工をします。

 

4-1.必要な情報のCSVを取得する。グループメンバー(GroupMember)、グループ(Group)、ユーザ(User)のCSVを取得します。それぞれ、D:SFGroupMember.csv、D:

SFGroup.csv、D:SFUser.csv として保存します。

4-2.Execute Queryでテーブルにロードします。

create table SFGroupMember as select * from csvread('D:SFGroupMember.csv');
create table SFGroup       as select * from csvread('D:SFGroup.csv');
create table SFUser        as select * from csvread('D:SFUser.csv');

4-3.SQLを実行し欲しい結果にする。

select * from
 SFGroupMember
inner join SFGroup parent
on SFGroupMember.GroupID = parent.ID
left join SFGroup child
on SFGroupMember.USERORGROUPID = child.ID
left join SFUser
on SFGroupMember.USERORGROUPID = SFUser.ID

上記SQLで、例えば、公開グループの中に公開グループがある場合や、公開グループの中にユーザがある場合など情報を取得することができます。(nullかどうかで出し分けするときれいに整形できると思います)