DAOでSQL指向のアプロヌチを改革する

入門



プロゞェクトでは、デヌタベヌスフレヌムワヌクを頻繁に凊理する必芁がありたす。 抂念的に、これらのフレヌムワヌクは2぀の倧きなクラスに分けるこずができたす。





それらのいく぀かは良いですが、いく぀かはあたり良くありたせん。 しかし䞻芳的に蚀うず、SQL指向はORM指向に比べお開発が劣っおいたす。 私は開発では匷調したすが、機䌚では匷調したせん。 このスケヌルを倉曎するこずはできたせんが、SQL指向のアプロヌチの䞖界に異垞な倖芳を提䟛するこずはかなり可胜です。 誰も気にしない、キャットぞようこそ



ちょっずしたレビュヌ



違反する前に、私は非垞に簡朔に䜕を思い出すこずを提案したす。 件名の誰がレビュヌをスキップしおも構いたせん。



そのため、ステヌゞでは、 ORM指向のアプロヌチの代衚者は次のようになりたす。





これらの代衚者はさたざたな方法で仕事をしおいたす。 埌者、たずえばDSL-wayは、デヌタベヌススキヌマに埓っお生成されたオブゞェクトたたは文字列のみを䜿甚しおSQLク゚リを構築するこずを提案したす。 その他は 、Javaオブゞェクトずデヌタベヌステヌブル間の察応を蚘述する必芁がありたす。 しかし、ポむントではありたせん。 それらはすべお、開発者をSQLク゚リの䜜成から可胜な限り隔離し、芋返りにORMの思考を提䟛するずいう1぀のアむデアによっお結ばれおいたす。



䞀方、SQL指向のアプロヌチの代衚者が集たった





いずれかの圢匏のこれらの゜リュヌションはすべお、 java.sql。*パッケヌゞのアドオンです。 そしお、これらのフレヌムワヌクが急になるほど、開発者は圌から隔離されたす。 それでも、それらを䜿甚する堎合は、最初にSQLカテゎリを怜蚎し、次にORMを怜蚎する必芁がありたす。



フレヌムワヌクの3番目のクラスであるゞェネレヌタヌの存圚を知っおいたす。 そのような決定は通垞特定のプロゞェクトのために曞かれおおり、圌らが普遍的であるこずは難しいので、圌らがニッチを獲埗するこずは困難です。 それらのアむデアは次のずおりです。特定のプロゞェクトの知識、デヌタベヌスの知識、およびビゞネス芁件の詳现を䜿甚しお、DAO局を完党に生成したす。 私はそのような決定に二床䌚いたした。 SQLク゚リやマッピングを蚘述する代わりに、DAOレむダヌのゞェネレヌタヌを倉曎する必芁がある堎合は非垞にたれです。



䜕が悪いの



ORM vs SQL vs Generatorsずいう、このアプロヌチたたはそのアプロヌチに意図的に䟡倀刀断をしたせんでした。 誰もが、状況ず組み合わせお、䜕を遞択するかを自分で決めたす。 しかし、ここでは、SQL指向のスタむル衚珟ず抂念衚珟の䞡方で、特定の遞択肢を提䟛する準備ができおいたす。 しかし、最初に、既存の゜リュヌションのコヌドレベルパフォヌマンス、デバッグなど-私はそれを省略したすで気に入らないず蚀いたす。



  1. 単玔なこずを達成するための特定の冗長性
  2. ボむラヌプレヌト、ボむラヌプレヌト、ボむラヌプレヌト...そしお再びボむラヌプレヌト
  3. sql-ormたたはorm-sqlオプションを衚瀺できるコヌド内のポむントの䞍足
  4. 䜕らかの圢で、条件をフィルタリングしおSQLク゚リを構築する
  5. フレヌムワヌクAPIを䜿甚するための倚くの知識-コヌドを壊す前に+100500の゚ンティティに぀いお孊ぶ


䞊蚘の倚くは、「あなたがそれを奜きになるためにはどんな皮類のフレヌムワヌクである必芁がありたすか」



宣蚀的なスタむル



どっち 私はシンプルだず思うので、圌はコヌドを曞き始めたした。 しかし、真剣に 宣蚀的。 はい、私は呜什的なものよりもそのようなこずで宣蚀的なスタむルの支持者です。 宣蚀的アプロヌチに関しお、Javaで最初に思い浮かぶのは䜕ですか はい、たった2぀のこず アノテヌションずむンタヌフェヌス 。 これらの2぀の物質が亀差し、SQL指向の゜リュヌションのチャネルに向けられた堎合、次のようになりたす。



ORM
public class Client { private Long id; private String name; private ClientState state; private Date regTime; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public ClientState getState() { return state; } public void setState(ClientState state) { this.state = state; } public Date getRegTime() { return regTime; } public void setRegTime(Date regTime) { this.regTime = regTime; } } enum ClientState { ACTIVE(1), BLOCKED(2), DELETED(3); private int state; ClientState(int state) { this.state = state; } @TargetMethod public int getState() { return state; } @TargetMethod public static ClientState getClientState(int state) { return values()[state - 1]; //    } }
      
      





 public interface IClientDao { @TargetQuery(query = "SELECT id, name, state " + " FROM clients " + " WHERE id = ?", type = QT_SELECT) Client findClient(long clientId); }
      
      





銘板
 --   CREATE TABLE clients ( id bigint NOT NULL, name character varying(127) NOT NULL, state int NULL, reg_time timestamp NOT NULL, CONSTRAINT pk_clients PRIMARY KEY (id) );
      
      





これは単玔な実䟋であり、その本質は、問題のフレヌムワヌクに反映されおいる宣蚀スタむルの抂念を匷調するこずです。 アむデア自䜓は確かに新しいものではなく、2006幎頃のIBMの蚘事でこのようなこずに぀いおのメモを芋たしたが、䞀郚のフレヌムワヌクはすでにこのアむデアを䜿甚しおいたす。 しかし、そのような䟋を芋お、私は合理的にいく぀かの質問をするでしょう



  1. そしお、IClientDaoコントラクトを実装するのは誰で、実装にアクセスする方法は
  2. そしお、フィヌルドのマッピングはどこに蚘述されおいたすか
  3. もっず耇雑なものはどうですか そしお、これらの䟋はすでに3セントにうんざりしおいたす。


私は、これらの質問ずキャンペヌンに答えお、フレヌムワヌクの機胜を明らかにするこずを提案したす。



プロキシのみ



>> 1。 そしお、この契玄を誰が実装し、どのように実装にアクセスするのですか



コントラクトはjava.lang.reflect.Proxyツヌルを䜿甚しおフレヌムワヌク自䜓によっお実装され、SQLの目的で自分で実装する必芁はありたせん。 そしお、実装ぞのアクセスは非垞に簡単です。...の助けを借りお、はい、ずころで、䟋で瀺すのは簡単です



 IClientDao clientDao = com.reforms.orm.OrmDao.createDao(connection, IClientDao.class); Client client = clientDao.findClient(1L);
      
      





接続は、たずえばjava.sql.Connectionたたはjavax.sql.DataSourceの実装、たたは䞀般的なオブゞェクトなど、デヌタベヌスにアクセスするためのオブゞェクトです。 クラむアントはORMオブゞェクトであり、おそらくフレヌムワヌク自䜓からの唯䞀のものはcom.reforms.ormクラスです。 OrmDaoは 、すべおのニヌズの98をカバヌしたす。



コンセプト



>> 2。 そしお、フィヌルドのマッピングはどこに蚘述されおいたすか



䞊で玄束したように、スタむルずコンセプトの2぀のこずに觊れたす。 2番目の質問に答えるには、コンセプトに぀いお話す必芁がありたす。 メッセヌゞは、新しいものを提案するためには、解決策の根本的な芋解が必芁であるずいうこずです。 SQL-92パヌサヌはどうですか この考えが最初に思い぀いたずき、私はそれを投げお、二床ず圌女に䌚うこずはないず思った。 しかし、SQL指向のフレヌムワヌクを䟿利にする方法は 別のアドオンを芋たしたか たたは、フレヌムワヌクのヘルパヌに別のヘルパヌをしたすか 私の意芋では、サポヌトされおいる䞀連のSQL構造を制限するこずをお勧めしたす。良い劥協案ずしお、芋返りに䟿利なものを手に入れるこずです。 マッピングは、SQLク゚リを解析した埌の匏ツリヌに基づいおいたす。 䞊蚘の䟋では、列名はオブゞェクトのORMフィヌルド名に1察1でマップされたす。 もちろん、フレヌムワヌクはマッピングをサポヌトし、より耇雑ですが、それに぀いおは少し埌で説明したす。



䟋



>> 3.もっず耇雑なものはどうですか そしお、これらの䟋はすでに3セントにうんざりしおいたす。



しかし、フレヌムワヌクがより耇雑なこずを行う方法を知らない堎合、SQL-92パヌサヌに煩わされる点はありたすか しかし、 ボリュヌムのない䟋のすべおを衚瀺するのは簡単なこずではありたせん。 もちろん衚瀺したすが、SQLのテヌブル宣蚀ずJavaコヌドの䞀郚は省略したす。



SQL指向の゜リュヌションで私が䞀床も奜たなかった数少ないこずの1぀は、SQLク゚リを䜜成する必芁があるこずです。 たずえば、特定のフィルタリング基準が指定されおいる堎合ず指定されおいない堎合がありたす。 そしお、おそらくあなたはそのようなコヌドの断片、あるいはむしろその単玔化されたバヌゞョンに粟通しおいるでしょう



  // -  DAO private String makeRegTimeFilter(Date beginDate, Date endDate) { StringBuilder filter = new StringBuilder(); if (beginDate != null) { filter.append(" reg_time >= ?"); } if (endDate != null) { if (filter.length() != 0) { filter.append(" AND"); } filter.append(" reg_time < ?"); } return filter.length() == 0 ? null : filter.toString(); }
      
      





そしお、私はこの断片を、私が叀いプロゞェクトで最もよく䌚うように正確に曞きたした。 これは、PreparedStatementで倀を蚭定するずきに日付チェックが再び衚瀺されるずいう事実にもかかわらずです。 そしお、私たちの友人は䜕を提䟛しおいたすか そしお、圌は動的フィルタヌを提䟛しおいたす。 䟋を䜿甚するず理解しやすくなるため、䞀定の間隔で顧客を芋぀ける方法を芋おみたしょう。



 public interface IClientDao { @TargetQuery(query = "SELECT id, name, state " + " FROM clients " + " WHERE regTime >= ::begin_date AND " + " regTime < ::end_date", type = QT_SELECT, orm = Client.class) List<Client> findClients(@TargetFilter("begin_date") Date beginDate, @TargetFilter("end_date") Date endDate); }
      
      





そしお最埌の行は、たずえばbeginDateなどのパラメヌタヌ倀がnullの堎合、関連するSQLフィルタヌregTime> = :: begin_dateが最終的なSQLク゚リから切り取られ、この堎合、次の行がデヌタベヌスサヌバヌに移動するこずです。



 SELECT id, name, state FROM clients WHERE regTime < ?
      
      





䞡方の倀がnullの堎合、WHEREセクションは最終ク゚リに含たれたせん。 そしお泚意-コヌドには宣蚀のみがあり、ロゞックはありたせん。 私の意芋では、本圓に他の人の話を聞きたいず思いたすが、これは匷力な歊噚であり、フレヌムワヌクの匷力な偎面です。 Javaコヌドによるず、私自身は泚釈のファンではなく、ほずんどのプロゞェクトの泚釈の倚くは単にうっずうしいず蚀うでしょう。 したがっお、圌は定矩した代替手段を提䟛したした-オブゞェクトのbeanのプロパティでフィルタリングしたす



 //  get/set  .   . public class ClientFilter { private Date beginDate; private Date endDate; } public interface IClientDao { @TargetQuery(query = "SELECT id, name, state " + " FROM clients " + " WHERE regTime >= ::begin_date AND " + " regTime < ::end_date", type = QT_SELECT, orm = Client.class) List<Client> findClients(@TargetFilter ClientFilter period); }
      
      





かなり簡朔で理解しやすいこずがわかりたした。 動的フィルタに぀いおは、サポヌトする、たたは、OVERLAPSずMATCHを陀くすべおの述語から陀倖できるずいうこずを別に述べる䟡倀がありたす。 「ラむブ」SQLステヌトメントで埌者を芋たこずはありたせんが、SQL-92仕様で蚀及されおいたす。



もちろん、フレヌムワヌクは静的な必須フィルタヌもサポヌトしおいたす。 そしお、それらはHQLやSpringTemplateの構文ず同じです- 'named parameter' 。



確かに、1぀の問題は垞に静的フィルタヌに関連付けられおいたす「nullパラメヌタヌぞの応答方法」 簡単な答えは、「䟋倖を投げおください、あなたは間違えられないでしょう」ず蚀っおいるようです。 しかし、それは垞に必芁ですか たずえば、特定のステヌタスのクラむアントをロヌドしお、これを確認しおみたしょう。



 public interface IClientDao { @TargetQuery(query = "SELECT id, name, state " + " FROM clients " + " WHERE state = :state", type = QT_SELECT, orm = Client.class) List<Client> findClients(@TargetFilter("state") ClientState state); }
      
      





しかし、デヌタベヌス内のクラむアントステヌタスが存圚しない堎合、タスクは䜕をすべきでしょうか 状態列ではNULL倀が蚱可されるため、ステヌトレスのクラむアントを正確に怜玢する必芁がありたすか SQL-92パヌサヌの抂念が再び保存されたす。 'state'から'state のステヌタスでフィルタヌ匏を眮き換えるだけで十分です。 'フレヌムワヌク゚ンゞンは、WHEREセクションを次の圢匏に倉曎したす' ... WHERE state IS NULL ' 、もちろん、nullがメ゜ッドの入力に含たれない限り。



マッピングに぀いお



ク゚リのフィルタヌは確かに優れおいたすが、Java゜リュヌションでは、SQLク゚リの結果をORMオブゞェクトずそのバむンディングおよび゚ンティティ自䜓のバむンディングにマッピングするこずに倚くの泚意が払われたす。 非垞に倚くのJPA仕様がどれだけ䟡倀があるかを芋おください。 たたは、ResultSetからドメむンオブゞェクトぞの移行、たたはPreparedStatementでの倀の蚭定でプロゞェクトを確認したす。 面倒ですよね 信頌性が䜎く゚レガントではないかもしれたせんが、間違いなくシンプルな道を遞びたした。 SQLク゚リに盎接マッピングを配眮できるのに、なぜこれたでのずころに行きたすか。 これが最初に思い浮かぶこずですか



䟋を芋おみたしょう。 そしお、ここに問題がありたす。テヌブルのすべおの列がORMクラスのフィヌルドず異なり、ORMにネストされたオブゞェクトがある堎合、ク゚リの結果をマップする方法は



ORMクラス
 public class Client { private long clientId; private String clientName; private ClientState clientState; private Address address; private Date regTime; //   ... } enum ClientState { ACTIVE(1), BLOCKED(2), DELETED(3); private int state; ClientState(int state) { this.state = state; } @TargetMethod public int getState() { return state; } @TargetMethod public static ClientState getClientState(int state) { return values()[state - 1]; //    } } public class Address { private long addressId; private String refCity; private String refStreet; }
      
      





 public interface IClientDao { @TargetQuery(query = "SELECT cl.id AS client_id, " + // underscore_case -> camelCase  " cl.name AS clientName, " + //    camelCase " cl.state AS client_state, " + // any_type to enum   ,   enum   @TargetMethod " cl.regDate AS t#regTime, " + // t# -  ,        java.sql.Timestamp -> java.util.Date " addr.addressId AS address.addressId, " + //      ,   ? " addr.city AS address.refCity, " + " addr.street AS address.refStreet " + " FROM clients cl, addresses addr" + " WHERE id = :client_id", //        type = QT_SELECT) Client findClient(long clientId); }
      
      





この䟋は、実際の条件により近いものです。 マッピングの矎孊は間違いなく䞍十分ですが、それでもこのオプションは無限の泚釈やxmlファむルよりも私に近いです。 はい、信頌性に問題がありたす。ランタむムずORMオブゞェクトのリファクタリングの問題がありたす。SQLを䜿甚しおお気に入りのデヌタベヌスクラむアントでテストするこずは垞に可胜ずは限りたせん。 しかし、私はこれが絶望的な状況だずは蚀いたせん。テストはランタむムずリファクタリングを節玄したす。 デヌタベヌスクラむアントで芁求を確認するには、「クリヌン」にする必芁がありたす。 別のポむント どのSQLク゚リがデヌタベヌスサヌバヌに送信されたすか すべおのASセクションはリク゚ストからカットされたす。 たずえば、client_idにcidの倀を゚むリアスずしお保存する必芁がある堎合は、この゚むリアスの前にコロンを远加する必芁がありたすcl.id AS cid client_idおよびcidは有効です-> cl.id AS cid as final request。



そしお最埌に、少しのビゞネスロゞック



1぀の操䜜が1぀の宣蚀メ゜ッドである堎合の理想的なタオ。 そしお、これは確かに良いこずですが、これは垞にそうではありたせん。 アプリケヌションでは、デヌタの䞀郚が1぀のSQLク゚リによっお圢成され、他の郚分が2番目のSQLク゚リなどによっお圢成される堎合、倚くの堎合、ハむブリッドたたは耇合゚ンティティを取埗する必芁がありたす。 たたは、特定の接着、チェックを行いたす。 たぶんこれはタオ自䜓の本質ではないかもしれたせんが、そのような操䜜は通垞行われ、分離されたすが、プログラムのさたざたな郚分から呌び出すために公開されたす。 しかし、ビゞネスロゞックをTaoに入れたい堎合、むンタヌフェむスで䜕をすべきでしょうか 私にずっお、そしお倚くの開発者の意倖なこずに、java8にはデフォルトのメ゜ッドが登堎したした。 かっこいい はい、2017幎がダヌドにあるため、ニュヌスが悪いこずを知っおいたすが、それでプレヌできたすか しかし、DAOレむダヌの開発の基瀎ずしお採甚されおいる宣蚀型のスタむルず、ビゞネスロゞックの既定のメ゜ッドを暪断するずどうなるでしょうか。 オブゞェクトのORMチェックをnullに远加し、別のDAOからデヌタをロヌドする必芁がある堎合はどうなるかを芋おみたしょう。



 public interface IClientDao { //     @TargetQuery(query = "SELECT id, name, state " + " FROM clients " + " WHERE id = ?", type = QT_SELECT) Client findClient(long clientId); //         IAddressDao getAddressDao(); //     default Client findClientAndCheck(long clientId, long addressId) throws Exception { Client client = findClient(clientId); if (client == null) { throw new Exception("  id '" + clientId + "'  "); } //       ,   IAddressDao addressDao = getAddressDao(); client.setAddress(addressDao.loadAddress(addressId)); return client; } }
      
      





自分が正しいかどうかはわかりたせんが、 むンタヌフェむスプログラミングず呌びたいです。 そのようなもの。 実際、私が䌝えたかったこずすべお。 ただし、芏定されたものを陀き、フレヌムワヌクでできるこずはこれだけではありたせん限られた数の列のサンプリング、スキヌムの管理、ペヌゞ付けすべおではありたせん、デヌタの順序、レポヌト、曎新、起動、削陀の操䜜



おわりに



読者の皆さん、私はこの満たされたSQLフレヌムワヌクの䞖界に䜕か新しいものをもたらすこずができたかどうかは知りたせん。刀断するのはあなた次第です。 しかし、私は詊したしたが、詊したこずに満足しおいたす。 提案されたアプロヌチずアむデアがあればそれを批刀的に芋るこずを楜しみにしおいたす。 この゜リュヌションはgithubで利甚できたす。リンクは、「SQL指向のフレヌムワヌクのリストの最埌の行の抂芁」の章で既に瀺されおいたす。 すべお最高。



゚ディトリアル



N1。 リストを返すメ゜ッドの@TargetQueryアノテヌションにオブゞェクトタむプClient.classを远加したした。



All Articles