まーしゃる
先のエントリーにあるメソッドmarshalの意味を調べる。
marshal:(名詞) 元帥
なんだそりゃ、と思ったが動詞だと「(人・軍隊を)整列させる、整頓する」という意味らしい。
だから元帥なのね。
戦いの意味合いを持っているなら、マーシャルアーツのマーシャルはこの単語?
違った・・・martial arts
martial:(形容詞) 勇ましい、好戦的な
ただ発音は同じらしい。
それにしてもなんでarts(芸術)なんだろう、と思ったらWikipediaに書いてあった。
「武芸」が英語になったからか。
マーシャルアーツ - Wikipedia
マーシャルアーツと言えば「テリー・ボガード」が連想される古い人間のワタシ。
Jersey(MOXy)でJSONバインドをカスタマイズする
JSONをリクエストで投げてJersey(MOXy)でPOJOとして受ける際に、Integerのフィールドに空文字が来ると「0」に変換される。
0じゃなくてnullで受けたい、というように変換をカスタマイズした場合はXmlAdapterを継承したadapterを作る。
参考:JAXBのXmlAdapterを自作する - Programming Studio
JerseyというかJAXBの仕様らしい。なのでアノテーションやクラスがXml・・・となってJSONで使うには違和感があるがちゃんと動作する。
で、@XmlJavaTypeAdapterでadapterクラスを指定する。
@XmlJavaTypeAdapterは@Target(value={PACKAGE,FIELD,METHOD,TYPE,PARAMETER})となっているので、指定する箇所で有効範囲が変わる。
いちおう試して動いたが、結局Backbone.stickitのonSetで空文字 => null変換して済ませちゃったのは内緒の話。
BeanManagerを取得する on Weld, Tomcat
Weld 2.3.1 + Jersey2.22.1
Tomcat8
発端はEntityに登録者IDや更新者IDを埋め込みたかったが、Entityで@SessionScopedなBeanを@Injectできなかったから。
EntityListener使ってみたり紆余曲折あったが、時間が無いので経過は省くとして結局BeanManagerを取得して直接Beanを取ってくることに。
参考その1:CDIのBeanManagerを使う - CLOVER
InitialContext ic = new InitialContext(); BeanManager bm = (BeanManager) ic.lookup("java:comp/BeanManager"); Set<Bean<?>> beans = bm.getBeans(SessionDto.class); Bean<?> bean = bm.resolve(beans); SessionDto sessionDto = (SessionDto) bm.getReference(bean, SessionDto.class, bm.createCreationalContext(bean)); // SessionDtoが@SessionScopedなクラス
これではlookup()するところでNamingExceptionになってできず。
おそらくTomcatで動かしているからだと思われる。
参考その2:jsf - BeanManager on Apache Tomcat 7.0.47 cannot create resource instance - Stack Overflow
Tomcatの場合、"java:comp/BeanManager"ではなく"java:comp/env/BeanManager"でlookupするとある。
確かにTomcatでDataSourceをlookupする時は"java:comp/env/・・・"で見に行く。
あとcontext.xmlにもResourceを追加する。
<Resource name="BeanManager" auth="Container" type="javax.enterprise.inject.spi.BeanManager" factory="org.jboss.weld.resources.ManagerObjectFactory" />
で、どうにかSessionDtoが取れた。
紆余曲折時間:約2時間
backbone.validationメモ
参考:thedersen/backbone.validation · GitHub
backbone 1.2.0
backbone.validation 0.11.5
backbone自体にvalidation機能はあるが、処理をいちいち書かないといけないし複雑化すると後から読むのも大変。
なのでbackbone.validationプラグインを使ってスッキリさせる。
Model
バリデーターはModelに「validation」プロパティで指定する。
key=Modelのkey、 value=バリデーター。
処理を記述する場合はfunctionを指定。戻り値にエラーメッセージを返す。
var model = Backbone.Model.extend({ validation: { name: function(value, attr, computedState) { if (value == '名無しの権兵衛') return '名前不正'; } } });
もしくは別でfunctionを作っておいてfunction名を指定。
validation: { name: 'validateName' }, validateName: function() {(略)}
よくあるバリデーターが用意されている。
validation: { name: { required: true }, // 必須 agreement: { acceptance: true }, // 許諾(true or 'true'のみ可。「同意しますか?」チェックボックスなんかで) age: { min: 1 }, // 最小(以上) age2: { max: 100 }, // 最大(以下) zipcode: { length: 7 }, // 桁数 password: { minLength: 8 }, // 最小桁数 password2: { maxLength: 256 }, // 最大桁数 password3: { rangeLength: [8, 256] }, // 桁数範囲 language: { oneOf: ['jp', 'en', 'others'] }, // どれかに該当 passwordRe: { equalTo: 'password' }, // 他のattributeと同値(passwordRe == 'password'ではなく、passwordRe == password) mail: { pattern: 'email' }, // 正規表現パターン(この場合はプラグイン定義済みのe-mailパターン) weight: { pattern: 'number' }, // 数値(小数も可) code: { pattern: 'digits' }, // 数字のみ(≠整数) /^\d+$/ url: { pattern: 'url' }, // url word: { pattern: /^[A-Z]/ } // 独自正規表現 }
エラー時のメッセージは用意されているが、独自メッセージを出したい場合はmsgを指定。
というかデフォルトメッセージは英語なので全部指定することになる。
name: { required: true, msg: '名前は必須です' // functionで文字列return、でも可 }
複数バリデーター指定もできる。
name: { required: true, minLength: 2, msg: '名前は必須で2文字以上' }
バリデーター毎にメッセージを変えたい場合は配列指定。
name: [{ required: true, msg: '名前は必須' },{ minLength: 2, msg: '名前は2文字以上' }]
bind
Viewにバインドする。
バインドするタイミングは、model or collectionがinitializeされた後ならいつでもいい。
Marionette + stickitを使っていて、onShow()でstickitのbindをしてあるので、bind並びでvalidationもそこでbindすることにする。
onShow: function() { this.stickit(); // stickitのbind Backbone.Validation.bind(this); // validationのbind }
Backbone.Validation.unbind(view)でunbindされる。
Modelのoverride
bindするとmodelのvalidate()とisValid()がオーバーライドされるので、自分で実装する必要がない。処理を書く手間が省けるのはやはり有難い。
あとpreValidate()が実装され、modelにsetする前に事前検証ができるようになる。
と言ってもstickit使っているとpreValidate()の使いドコロが無い。
とりあえずsave前にvalidate()を呼んでエラー表示することにする。
ポート番号8888
「昨日アップされたソース取り込んで動かしたら変なサイトに飛ぶんだけど」
いやそんなの仕込んだ覚えないし。でも本当に「free-merchants.com」というサイトに飛ばされている。それに自分のローカル環境では起きない。
「chromeのプラグインが悪さしてんのかなー」と思いプラグインを外してもらうと正常に。
どうやらこれが該当する様子。
New Tab Page redirects localhost:8888 to spam website
日付は2日前、バージョンアップでプラグイン周りがおかしくなったのかも。
確かに開発のローカル環境でwebサーバーを8888ポートであげていた。
プラグイン外して現象は起きなくなったが、念のためポートを変えることにする。
wikiに49152–65535がプライベート ポート番号と書いてある。
TCPやUDPにおけるポート番号の一覧 - Wikipedia
「へー、知らなかったなー」(吉田羊)
「あれ?知らなかったんですか?」(田中みな実)
吉田羊ほんとよく見るなー。ポカリのCMわりと好き。
・・・とりあえずプライベート番号から選んでおく。
結局chromeの現象ってプラグインが悪いのか本体が悪いのかどっちなんだ?
あんまり話題になってないてことはプラグイン側か?
プラグイン便利は便利だけど、やっぱり程々にしないと危険。
JPQLのfetch その2
JOIN (≠JOIN FETCH)
JPQLはJOIN FETCHではなくJOINだけでも書ける。
@NamedQuery(name="Find", query="SELECT DISTINCT t FROM Team t JOIN t.members m")
SELECT DISTINCT t1.ID, t1.NAME FROM member t0, team t1 WHERE (t0.teamId = t1.ID) 1 Aチーム SELECT ID, NAME, teamId FROM member WHERE (teamId = ?) bind => [1] ハンニバル フェイス B.A. マードック ----- 2 MP SELECT ID, NAME, teamId FROM member WHERE (teamId = ?) bind => [2] リンチー デッカー フルブライト -----
FETCHしていないのでN+1になる。使いドコロとしてはWHERE条件にmemberを加えたいけどFETCHはしない場合など。
@NamedQuery(name="Find", query="SELECT DISTINCT t FROM Team t JOIN t.members m WHERE m.name LIKE '%B%'") List<Team> temas = em.createNamedQuery("Find", Team.class).getResultList(); for (Team t : teams) { System.out.println(t.getId()); System.out.println(t.getName()); System.out.println("-----"); }
SELECT t.ID, t1.NAME FROM member t0, team t1 WHERE (t0.NAME LIKE ? AND (t0.teamId = t1.ID)) bind => [%B%] 1 Aチーム -----
QueryHints.FETCH (eclipselink)
JPA上ではFETCHは子プロパティまでで、孫プロパティをJOIN FETCHしてもN+1になるが、QueryHintでEclipseLink独自ヒントQueryHints.FETCHを使うと、多段FETCHしてくれるらしい。
テーブルを増やして実験。
team
id | name |
---|---|
1 | Aチーム |
2 | MP |
member
id | name | teamId | titleId |
---|---|---|---|
1 | ハンニバル | 1 | 1 |
2 | フェイス | 1 | 2 |
3 | B.A. | 1 | 3 |
4 | マードック | 1 | 4 |
101 | リンチ | 2 | 1 |
102 | デッカー | 2 | 1 |
103 | フルブライト | 2 | 5 |
title
id | name |
---|---|
1 | 大佐 |
2 | 中尉 |
3 | 軍曹 |
4 | 大尉 |
5 | 准将 |
@Entity @Table(name="team") public class Team { @Id private Integer id; private String name; @OneToMany(mappedBy="team") private List<Member> members; } @Entity @Table(name="member") public class Member { @Id private Integer id; private String name; @ManyToOne @JoinColumn(name="teamId") private Team team; @ManyToOne @JoinColumn(name="titleId") private Title title; } @Entity @Table(name="title") public class Title { @Id private Integer id; private String name; @OneToMany(mappedBy="title") private List<Member> members; } ※getter, setter略
まずはhint無し。
@NamedQuery(name="Find", query="SELECT DISTINCT t FROM Team t JOIN FETCHt.members m JOIN FETCH m.title ti") List<Team> temas = em.createNamedQuery("Find", Team.class).getResultList(); for (Team t : teams) { System.out.println(t.getId()); System.out.println(t.getName()); t.getMembers().forEach(m -> System.out.println("\t" + m.getName() + m.getTitle().getName())); System.out.println("-----"); }
SELECT DISTINCT t1.ID, t1.NAME, t0.ID, t0.NAME, t0.teamId, t0.titleId FROM member t0, team t1 WHERE (t0.teamId = t1.ID) SELECT ID, NAME FROM title WHERE (ID = ?) bind => [4] SELECT ID, NAME FROM title WHERE (ID = ?) bind => [2] SELECT ID, NAME FROM title WHERE (ID = ?) bind => [1] SELECT ID, NAME FROM title WHERE (ID = ?) bind => [3] SELECT ID, NAME FROM title WHERE (ID = ?) bind => [5] 1 Aチーム マードック大尉 フェイス中尉 ハンニバル大佐 B.A.軍曹 ----- 2 MP デッカー大佐 フルブライト准将 リンチ大佐 -----
孫プロパティのtitleは個別にSELECTが実行されている。
hintを付ける。
@NamedQuery(name="Find", query="SELECT DISTINCT t FROM Team t JOIN FETCH t.members m JOIN FETCH m.title ti" , hints = {@QueryHint(name = QueryHints.FETCH, value = "t.members.title")})
SELECT DISTINCT t1.ID, t1.NAME, t0.ID, t0.NAME, t0.teamId, t0.titleId, t2.ID, t2.NAME FROM member t0, title t2, team t1 WHERE ((t0.teamId = t1.ID) AND (t2.ID = t0.titleId)) 1 Aチーム マードック大尉 フェイス中尉 ハンニバル大佐 B.A.軍曹 ----- 2 MP デッカー大佐 フルブライト准将 リンチ大佐 -----
SELECT 1回でFETCHしている。
QueryHints.FETCH の裏効果
QueryHints.FETCHを使うと、自動でDISTINCT, JOIN FETCH状態になる。
@NamedQuery(name="Find", query="SELECT t FROM Team t JOIN t.members m JOIN m.title ti" , hints = {@QueryHint(name = QueryHints.FETCH, value = "t.members.title")})
SELECT t1.ID, t1.NAME, t0.ID, t0.NAME, t0.teamId, t0.titleId, t2.ID, t2.NAME FROM member t0, title t2, team t1 WHERE ((t0.teamId = t1.ID) AND (t2.ID = t0.titleId)) 1 Aチーム ハンニバル大佐 フェイス中尉 B.A.軍曹 マードック大尉 ----- 2 MP リンチ大佐 デッカー大佐 フルブライト准将 -----
SQLにDISTINCTは付いていないが、グルーピングされて取得されているし、JOIN(≠JOIN FETCH)だがSELECT 1回で全部取得している。
hintがなければDISTINCTもFETCHもされない。
ハマったのはこの裏効果で、hint付きでJOIN(≠JOIN FETCH)をしていたJPQLを修正した際、孫プロパティが不要になったのでhintを外したら、FETCHもDISTINCTもされなくなったという。。。
QueryHints.FETCHの仕様が変わる可能性もあるし、DISTINCT, FETCHは必要に応じてちゃんと付けましょうという教訓。
JPQLのfetch その1
タイトルの通りだが勉強不足でハマったのでメモ。
Java8 + JPA2.1 + eclipselink2.6
テーブル
team
id | name |
---|---|
1 | Aチーム |
2 | MP |
member
id | name | teamId |
---|---|---|
1 | ハンニバル | 1 |
2 | フェイス | 1 |
3 | B.A. | 1 |
4 | マードック | 1 |
101 | リンチ | 2 |
102 | デッカー | 2 |
103 | フルブライト | 2 |
Entity
@Entity @Table(name="team") public class Team { @Id private Integer id; private String name; @OneToMany(mappedBy="team") private List<Member> members; } @Entity @Table(name="member") public class Member { @Id private Integer id; private String name; @ManyToOne @JoinColumn(name="teamId") private Team team; } ※getter, setter略
1件取得
@NamedQuery(name="Find", query="SELECT t FROM Team t WHERE t.id = :id") Team t = em.createNamedQuery("Find", Team.class).setParameter("id", 1).getSingleResult(); System.out.println(t.getId()); System.out.println(t.getName()); t.getMembers().forEach(m -> System.out.println("\t" + m.getName()));
実行ログ
SELECT ID, NAME FROM team WHERE (ID = ?) bind => [1] 1 Aチーム SELECT ID, NAME, teamId FROM member WHERE (teamId = ?) bind => [1] ハンニバル フェイス B.A. マードック
JPQLでJOINしていないのでgetMembers()の際にSQLが発行される、N+1問題というやつ。
なのでクエリでJOIN FETCHしてやると、memberもまとめて取ってくる。
@NamedQuery(name="Find", query="SELECT t FROM Team t JOIN FETCH t.members WHERE t.id = :id")
SELECT t1.ID, t1.NAME, t0.ID, t0.NAME, t0.teamId FROM member t0, team t1 WHERE ((t1.ID = ?) AND (t0.teamId = t1.ID)) bind => [1] 1 Aチーム ハンニバル フェイス B.A. マードック
複数件取得
@NamedQuery(name="Find", query="SELECT t FROM Team t") List<Team> temas = em.createNamedQuery("Find", Team.class).getResultList(); for (Team t : teams) { System.out.println(t.getId()); System.out.println(t.getName()); t.getMembers().forEach(m -> System.out.println("\t" + m.getName())); System.out.println("-----"); }
SELECT ID, NAME FROM team 1 Aチーム SELECT ID, NAME, teamId FROM member WHERE (teamId = ?) bind => [1] ハンニバル フェイス B.A. マードック ----- 2 MP SELECT ID, NAME, teamId FROM member WHERE (teamId = ?) bind => [2] リンチー デッカー フルブライト -----
1行と同様、N+1でデータを取得してくる。
なのでJOIN FETCHをする。
@NamedQuery(name="Find", query="SELECT t FROM Team t JOIN FETCH t.members")
SELECT t1.ID, t1.NAME, t0.ID, t0.NAME, t0.teamId FROM member t0, team t1 WHERE (t0.teamId = t1.ID) 1 Aチーム ハンニバル フェイス B.A. マードック ----- 1 Aチーム ハンニバル フェイス B.A. マードック ----- 1 Aチーム ハンニバル フェイス B.A. マードック ----- 1 Aチーム ハンニバル フェイス B.A. マードック ----- 2 MP リンチー デッカー フルブライト ----- 2 MP リンチー デッカー フルブライト ----- 2 MP リンチー デッカー フルブライト -----
なんてこったい。
ということを無くすためにDISTINCTをつける。
@NamedQuery(name="Find", query="SELECT DISTINCT t FROM Team t JOIN FETCH t.members")
SELECT DISTINCT t1.ID, t1.NAME, t0.ID, t0.NAME, t0.teamId FROM member t0, team t1 WHERE (t0.teamId = t1.ID) 1 Aチーム ハンニバル フェイス B.A. マードック ----- 2 MP リンチー デッカー フルブライト -----
SQLにdistinctがついた上で、オブジェクトレベルでもTeamでグループ化される。
その2へ続く