ブログを書くことを主張することは本当に難しいことがわかりました。さまざまな理由がブログの世話をする必要はありません。私はもともと、ORMのDIY実装を書いて時間を見ることを計画していました。最初に動的SQLを実装し、次回時間があるときにORMの完全な実装を追加したいと思います。
MyBatisを使用した人は、おそらく動的なSQLに精通しています。使用していない場合は、楽しみを見てください。 MySQLと初めて接触したのは、私が4年生だったときでした。当時、私はダイナミックSQLが非常に素晴らしく柔軟であると思いました。私はいつもそれを実装する方法を見つけたかったのです。当時はIOC、MVC、および単純なORMフレームワークを書くことができましたが(MyBaitsを模倣しますが、動的なSQLパーツはありません)、MyBatisのコアにダイナミックSQLを実装する場所とそれを実装する方法をまだ見つけることができませんでした。たぶん、コードが絡みすぎていて、私はそれをまったく理解できませんでした。今まで、私はMyBatisの動的なSQL部分を読む勇気を持っていませんでした。たぶん私はアルゴリズムに不可解なa敬の念を抱いて生まれました。
数年前、私は構成プラットフォームを構築したいと思っていて、解析言語を使用してJavaの実装を置き換えたいと思っていたため、構成担当者はページに少量のコードを簡単に記述して複雑なビジネスロジック(データベース操作を含む)を実装できました。当時、JavaはすでにJS解析エンジンを持っていましたが、ほとんどの人は効率が低すぎると言いました。自分が何について夢中になっているのかわからなかったら、自分で解析言語を実装することを考えました。しかし、私はいつも自分の言語を実現することを夢見てきました。コンパイルされた言語よりも分析言語から始める方が簡単なので、私はそれを決定的に行い始めました。それを書いた後、私は自分の実装がおそらくその時のJSエンジンほど効率的ではないことに気付きました。当時、私は本当に若くてシンプルでした。今日私が話している動的なSQL実装は、実際にはその時の解析言語に触発されています。
ダイナミックSQLについて多くのナンセンスを言わずに話しましょう。次の例をご覧ください。まず、ここでの例はSQLを書く正しい方法ではないことを宣言します。可能な限り複雑なネストされた構造を書きたいだけです。この複雑な状況が実装されている場合、シンプルにすることはさらに困難です。
pl_pagewidget <if test = "widgetcodes!= null"> where pagewidgetcode in <foreach collection = "widgetcodes" item = "index" open = "(" separator = "、" close = ")"> <if test = "index == 0"> {item = "bs" open = "(" separator = "、" close = ")">#{b} </foreach> </foreach> </if> <if> "a!= null">およびa =#{a} </if>上記の例を解析するためにSQLを実装するために、難易度の1つは、テスト属性の真または誤った条件を決定する方法に似ています。ただし、この困難は、Struts2で学んだOGNL式の前にあります。友人がかなり奇妙な現象に遭遇したかどうかはわかりません。つまり、次の式がMyBatis Dynamic SQLで書かれていることもありますが、n = 0の場合、実際に条件を満たしている場合、つまりテストの値は偽で、0はこの式の条件を満たすことができません。これがOGNLライブラリの理由です。このように再生する方法はありません。特別な状況として覚えておいてください
test = "n!= null and n!= ''"
OGNL式は次のように使用するのに非常に便利です
Import java.util.hashmap; import java.util.map; import ognl.ognl; public class ognltest {// output result:false public static void main(string [] args)throws {string con1 = "n!= null and n!= ''"; map <string、object> root = new Hashmap <>(); root.put( "n"、0); System.out.println(ognl.getValue(con1、root)); }}上記の例を解析するためにSQLを実装するには、2番目の難しさは、このSQLがXMLの層で覆われているが、次のように標準のSQLであることです。
<sql> pl_pagewidgetから削除<if test = "widgetcodes!= null"> where pagewidgetcode in <foreach collection = "widgetcodes" item = "item" index = "index" open = "(" sepraint = ")"> <if test = "index == 0" index = "index1" open = "(" separator = "、" close = ")">#{b} </foreach> </foreach> </if> <if> <if> <"a!= null">およびa =#{a} </if> </sql> </sql>ただし、上記のXMLを解析することは、通常のXMLとは異なります。このXMLは、タグとテキストの混合です。通常、このXMLの解析を開発することはめったにありません。ただし、XML Dom4Jを解析するために一般的に使用されるツールは、実際にこの種のSQLを非常にうまく解析できますが、使用することはほとんどありません。要素クラスのContent()メソッドは、ノードのコレクションを返すことができ、このコレクションを通過して各ノードのタイプを判断できます。これらの2つの重要なポイントを解決した後、この動的なSQLを解析するために少しのトリックを追加する必要があります。
私が使用したトリックは、Java構文形式に触発されました。たとえば、Javaにはローカル変数とグローバル変数があり、参照の合格の状況は考慮されていません。グローバル変数int i = 1の場合;グローバル変数はメソッドに渡され、メソッドで変更されます。この方法で見られるのは値の変更ですが、メソッドの外側に表示されるのはまだ1です。実際、Javaを学んだ後にこの現象を知っておく必要があります。また、メソッドが呼び出されると、メソッドにグローバル変数とローカル変数が表示されます。メソッドコールが終了した後、ローカル変数がクリアおよびリリースされます(ごみ収集器をご覧ください)。これらを導入し、コードを直接追加しました
java.io.stringreader; Import java.text.simpledateformat; Import java.util.arrays; Import java.util.date; Import java.util.hashmap; Import java.util.list; Import java.util.map; Import java.util.util.til.matcher. org.apache.commons.collections.maputils; Import org.apache.commons.lang.stringutils; Import org.dom4j.document; Import org.dom4j.element; Import org.dom4j.node; intall org.dom4j.text; Import org.dom.dom.dom.dom.dom.dom.dax.saxleder.sax.saxleder; com.rd.sql.basenode; import com.rd.sql.nodefactory; public class sqlparser {private map <string、object> currparams = new hashmap <string、object>(); /** pl_pagewidgetから削除<if test = "widgetcodes!= null">ここで、pagewidgetcode in <foreach collection = "widgetCodes" item "item" index = "index" open = "(" seproporator = "、" close = ")"> <if test = "index == 0"> {index = "" bs " open = "(" separator = "、" close = ")">#{b} </foreach> </foreach> </foreach> </if> <if> <if> <if> <"a!= null">およびa =#{a} </if> */public static void main(string [] args)throws {map <string、object> = new Hashmap <String> map.put( "widgetcodes"、arrays.aslist( "1"、 "2")); map.put( "bs"、arrays.aslist( "3"、 "4")); map.put( "a"、1); sqlparser parser = new sqlparser(); system.out .println(parser.parser( "pl_pagewidget/n" + " +" + "/t <if test =/" widgetcodes!= null/">/n"/t/twhere pagewidgetcode in/n " +" + "/t <foreach collection =/" item =/"/"/"/"/"/"/"/」 Separator =/"close =/")/">/n" + "/t/t <if test =/" index == 0/">/n" + "/t/t#{item}/n" + "/t/t </n" + " +"/t/t <foreach collection =/"bs/" bs/"b/" index =/"open/"/"/" close =/")/">/n " +"/t/t#{b}/n " +"/t/t </foreach>/n " +"/t/t </foreach>/n " +"/t </n " +"/t <if test =/"a!= null/">/n " +"マップ)); System.out.println(parser.getParams()); } public String Parser(String XML、Map <String、Object> Params)スロー例外{// xml = "<?xml version =/" 1.0/"encoding =/" utf-8/"?>"+xml; //入力動的sql xml = "<sql>"+xml+"</sql>"のxmlタグのレイヤーを設定します。 saxreader reader = new Saxreader(false); document document = reader.read(new StringReader(XML));要素要素= document.getRootelement(); map <string、object> currparams = new hashmap <string、object>(); stringbuilder sb = new StringBuilder(); // ParserElement(要素、Currparams、Params、SB)を開始します。 return sb.tostring(); } /** *再帰パーサーを使用してダイナミックSQL * @param ELE1 XMLタグを解析する * @param currparams * @param globalparams * @param sb * @throws例外 * /private void parserelement(element ele1、map <<string、object> currparams、spring、object> global params、shrparams、shb)たとえば、ノードを解析すると、IFノードを解析しますが、テストがtrueを決定した場合、trueを返します。 Tempval val = ParseroneElement(Currparams、GlobalParams、ELE1、SB); //ノードの抽象ノードオブジェクトは、ベースノードnode = val.getNode(); /***実際、この文の上のステートメントはXMLタグを解析し、タグ内のコンテンツを解析しません。ここで *コンテンツを解析する前に、事前操作がある場合、操作前 */ node.pre(currparams、globalparams、ele1、sb)を行うことを意味します。 //たとえば、テスト結果が真の場合、ノード内のコンテンツを解析する必要があるかどうかを防御します。 //通常のテキストリスト<node> nodes = ele1.content()を含む、このノードの下にあるすべての子ノードのコレクションを取得します。 if(flag &&!nodes.isempty()){ /***これは、ノード内のコンテンツをさらに解析することを意味します。ノードをメソッドのシェルに比較できます*内側のコンテンツは、メソッドの特定のステートメントに類似しています。ノードのコンテンツを解析する前に、このノードの下にローカルパラメーターを含むコンテナを作成します。最も便利なのは、もちろんmap */ map <string、object> params = new hashmap <string、object>();です。 /***この例のパラメーターは一般的なデータ型であるため、局所パラメーターを外側に直接コンテナに入れます*参照タイプがないため、これはコピーに相当します。外側*に渡されたオブジェクトに影響を与えないために、メソッドが着信パラメーター*/ params.putall(currparams)を呼び出す場合を比較できます。 //すべての子ノードのループ(int i = 0; i <nodes.size();){node n = nodes.get(i); //ノードが通常のテキストの場合if(n instanceof text){string text =((text)n).getStringValue(); if(stringutils.isnotempty(text.trim())){//処理#{xx}などのテキストをトレーニングし、$ {yy}をsb.append(handtext(text、params、globalparams))で渡された実際の値に直接置き換えます。 } i ++; } else if(n instanceof element){要素e1 =(要素)n; // XMLの子要素ParserElement(E1、Params、GlobalParams、SB)を再帰的に解析します。 //ループフラグが真でない場合、次のタグを解析する//これは、ループタグを繰り返し解析する必要があることを意味します。そうでなければ、次の要素を処理し続けますwhen_flag = maputils.getboolean(params、attrs.while_flag、false); if(!while_flag ||!nodefactory.iswhile(n.getname())|| e1.attributevalue(attrs.index)== null ||!e1.attributevalue(attrs.index).equals(paramseg(attrs.while_index)){i ++; }}} //ノード処理node.after(currparams、globalparams、ele1、sb)の後に何をすべきか。 //現在のスコープパラメーターparams.clear()をリサイクルします。 params = null; }} /** *パラメーターを置き換えるプロセステキスト#{item} * @param str * @param params * @return * /private string handtext(string str、map <string、object> params、obje。整数インデックス= null; if(stringutils.isnotempty(indexstr)){index = maputils.getinteger(params、indexstr); } // match#{a} parameter string reg1 = "(#// {)(// w+)(//})"; // $ {a} string reg2 = "(// $ // {)(// w+)(//})のパラメーターを一致させます。パターンP1 = pattern.compile(reg1); Matcher M1 = P1.Matcher(STR);パターンP2 = pattern.compile(reg2); Matcher M2 = P2.Matcher(STR);文字列whileList = maputils.getString(params、attrs.while_list); map <string、object> allparams = getallparams(params、globalparams); while(m1.find()){string tmpkey = m1.group(2); string key = whilelist == null?tmpkey :( whilelist+"_"+tmpkey); key = index == null?key:(key+index); string rekey = "#{"+key+"}"; // foreachと同様のループで、パラメーター#{xx}を#{xx_0}、#{xx_1} str = str.replace(m1.group(0)、rekey)に置き換える必要がある場合があります。 Currparams.put(key、allparams.get(tmpkey)); } while(m2.find()){string tmpkey = m2.group(2);オブジェクト値= AllParams.get(tmpkey); if(value!= null){str = str.replace(m2.group(0)、getValue(value)); }} return str; } private string getValue(オブジェクト値){string result = ""; if(value instanceof date){simpledateFormat sdf = new simpledateFormat( "yyyy-mm-dd hh:mm:ss"); result = sdf.format((date)value); } else {result = string.valueof(value); } return result; } private Map <string、object> getallparams(Map <String、Object> Currparams、Map <String、Object> GlobalParams){Map <String、Object> AllParams = new Hashmap <String、object>(); AllParams.putall(GlobalParams); AllParams.putall(Currparams); AllParamsを返します。 } // XML要素を解析するprivate tempval parserOneElement(Map <String、Object> Currparams、Map <String、Object> GlobalParams、Element ELE、StringBuilder SB)スロー例外{// XMLタグ名String ELENAME = ELE.GETNAME(); //ノードを解析した後も続きますか? IFのようなノードに遭遇した場合、テストが空であるかどうかを判断する必要があります。 boolean iscontinue = false; //抽象ノードBasenode node = nullを宣言します。 if(stringutils.isnotempty(elename)){//ノードファクトリを使用して、ノードnode node node = nodefactory.create(elename)などのノード名に基づいてノードオブジェクトを取得します。 //このノードを分析し、ノード内のコンテンツを解析する必要があるかどうかを返します= node.parse(currparams、globalparams、ele、sb); } new tempval(iscontinue、ele、node)を返します。 } public map <string、object> getParams(){return currparams; } / *** XML要素が解析された後に結果をカプセル化します* @author rongdi* / final static class tempval {private boolean iscontinue;プライベート要素ELE;プライベートベースノードノード。 public tempval(boolean iscontinue、element ele、basenodeノード){this.iscontinue = iscontinue; this.ele = ele; this.node = node; } public boolean iscontinue(){return iscontinue; } public void setcontinue(boolean iscontinue){this.iscontinue = iscontinue; } public element getele(){return ele; } public void setele(element ele){this.ele = ele; } public basenod getNode(){return node; } public void setNode(basenode node){this.node = node; }}} Import org.dom4j.element; import java.util.hashmap; Import java.util.map;/*** abstract node* @author rongdi*/public abstract class Basenode {public <string、object> object> currparams、map <string、object> globalparams、globalparams、ele public void pre(map <string、object> currparams、map <string、object> globalparams、element ele、stringbuilder sb)throws exception {} public void afted(map <string、object> currparams、map <string、object> globalparams、element ele、stringbuilder sb)例外{} < map <string、object> globalparams){map <string、object> allparams = new hashmap <string、object>(); AllParams.putall(GlobalParams); AllParams.putall(Currparams); AllParamsを返します。 }} java.util.map; Import ognl.ognl; import org.apache.commons.lang.stringutils; import org.dom4j.element;/*** if node* @author rongdi*/public class ifnodeがbasenode {@override public boolean(@override parmes parse( @oble <string> object> currimmes、baseNode) ele、stringbuilder sb)スロー例外{// node string teststr = ele.attributevalue( "test")のテスト属性を取得します。ブールテスト= false; try {if(stringutils.isnotempty(testStr)){//グローバル変数とローカル変数Map <String、Object> AllParams = GetAllParams(Currparams、GlobalParams); // ognlを使用して、trueまたはfalse test =(boolean)ognl.getValue(testStr、allparams)を決定します。 }} catch(例外e){e.printstacktrace();新しい例外をスローする(「裁判官運用パラメーター」+testStr+「違法」); } if(ele.content()!= null && ele.content()。size()== 0){test = true; }戻りテスト。 }} java.util.arraylist;インポートjava.util.hashmap; Import java.util.list; Import java.util.map; Import java.util.set; Import ognl.ognl; Import org.apache.commons.celections.maputils; Import org.apache.commons.lang.lang.lang.lang.lang.stringtils org.dom4j.element;/** foreachノードの属性は、次の通りであり、走行する必要があるコレクションアイテムです。コレクションを横断した後、各要素に保存されている変数インデックス。コレクションのインデックス番号は、0、1、2などです...移動後のセパレーター、指定されたセパレーターで開きます。トラバーサルの後にスプライシングを開始するシンボルは次のとおりです(トラバーサル後にスプライシングを終了するシンボルを閉じます) string collectionstr = ele.attributevalue( "collection"); string itemstr = ele.attributevalue( "item");文字列index = ele.attributevalue( "index"); string separatorstr = ele.attributevalue( "separator"); string openstr = ele.attributevalue( "open");文字列closeStr = ele.attributevalue( "close"); if(stringutils.isempty(index)){index = "index"; } if(stringutils.isempty(separatorstr)){separatorstr = "、"; } if(stringutils.isnotempty(openstr)){currparams.put(attrs.while_open、openstr); } if(stringutils.isnotempty(closestr)){currparams.put(attrs.while_close、closestr); } if(stringutils.isnotempty(collectionstr)){currparams.put(attrs.while_list、collectionstr); } currparams.put(attrs.while_separator、separatorStr); if(index!= null){ /***ローカル変数に現在のループ変数の値がある場合、ループラベルを入力したのは初めてではないことを意味します。 startタグ *を削除し、ローカル変数値 */ if(currparams.get(index)!= null){currparams.remove(attrs.while_start); Currparams.put(index+"_"、(integer)currparams.get(index+"_")+1); } else {//初めてループラベルCurrparams.put(attrs.while_start、true)を入力するとき。 Currparams.put(index+"_"、0); } currparams.put(index、(integer)currparams.get(index+"_")); } boolean条件= true; Map <string、object> allparams = getallparams(currparams、globalparams);オブジェクトコレクション= null; if(stringutils.isnotempty(collectionStr)){//コレクションをループしてコレクション= ognl.getValue(collectionstr、allparams); //コレクションプロパティが空ではないが条件がnullの場合、デフォルトで境界条件が追加されます(stringutils.isempty(条件)){//ここでは、コレクションを使用してそれを実証します。配列を追加することもできますが、.length if(collection instanceof list){condrystr = index+"_ <"+collectionstr+"。size()"; } else if(Collection Instanceof Map){Map Map =(Map)Collection; set set = map.entryset();リストリスト= new arrayList(set); AllParams.put( "_ list_"、list);条件ストア= index+"_ <_ list _"+"。size()"; }}} currparams.remove(attrs.while_end); if(stringutils.isnotempty(条件)){//計算条件の値=(boolean)ognl.getvalue(conditionstr、allparams); map <string、object> tempmap = new Hashmap <>(); tempmap.putall(allparams); tempmap.put(index+"_"、(integer)currparams.get(index+"_")+1); Currparams.put(attrs.while_end、!(boolean)ognl.getValue(conditionstr、tempmap)); } boolean flag = true; Currparams.put(attrs.while_index、index); Currparams.put(attrs.while_flag、true); if(condition){try {if(stringutils.isnotempty(itemstr)&& stringutils.isnotempty(collectionstr)){object value = null; int idx = integer.parseint(currparams.get(index+"_")。toString()); if(collection instanceof list){value =((list)collection).get(idx); Currparams.put(itemstr、value); } else if(Collection Instanceof Map){Map Map =(Map)Collection; set <map.entry <string、object >> set = map.entryset(); List <Map.Entry <String、Object >> list = new ArrayList(set); Currparams.put(itemstr、list.get(idx).getvalue()); Currparams.put(index、list.get(idx).getkey()); }}} catch(Exception e){throw new Exception( "コレクションまたはマップから値を取得"+currparams.get(index)+"error"+e.getmessage()); }} else {flag = false; DestroyVars(Currparams、Index、Itemstr); } flagを返します。 } / ***ループタグを初めて入力するときの場合、Openのコンテンツをスペル* / @Override public void pre(map <string、object> currparams、map <string、object> globalparams、element ele、stringbuilder sb)スロー例外{super.pre(currparams、globalams、ele、sb); boolean start = maputils.getboolean(currparams、attrs.while_start、false); if(start){string open = maputils.getString(currparams、attrs.while_open); sb.append(open); }} / ***ループラベルが最終的に入力された場合、クローズの内容は最後に綴られますboolean end = maputils.getboolean(currparams、attrs.while_end、false); string separator = maputils.getString(currparams、attrs.while_separator); if(!end && stringutils.isnotempty(separator)){sb.append(separator); } if(end){string close = maputils.getString(currparams、attrs.while_close); if(sb.tostring()。endswith(separator)){sb.deletecharat(sb.length() - 1); } sb.append(close); }} //一時的な変数のプライベートボイドDestrovevars(Map <String、Object> Currparams、String Index、String varstr){Currparams.Remove(attrs.while_index); Currparams.remove(attrs.while_flag); Currparams.Remove(attrs.while_separator); Currparams.Remove(attrs.while_start); Currparams.Remove(attrs.while_end); Currparams.Remove(attrs.while_list); }} import org.dom4j.element; Import java.util.map; public class sqlnodeはbasenode {@override public boolean parse(@override public boolean parse(Map <String、Object> Currparams、Map <String、Object> GlobalParams、Element ELE、StringBuilder SB)スロー例を投げます。 }} Import java.util.arrays; import java.util.list; import java.util.map; import java.util.util.concurrent.concurrenthashmap;/*** node Factory*/public class nodefactory {private static map <string、basenode> nodemap = new concurrenthashmap <string>プライベート最終静的リスト<String> whileLeList = arrays.Aslist( "foreach"); static {nodemap.put( "if"、new ifnode()); nodemap.put( "sql"、new sqlnode()); nodemap.put( "foreach"、new foreachnode()); } public static boolean iswhile(string elementname){return whilelist.contains(elementName); } public static void addNode(string nodename、basenode node){nodemap.put(nodename、node); } public static basenod create(string nodename){return nodemap.get(nodename); }}/***さまざまなタグ* @author rongdi*/public class attrs {public final static string transactional = "Transactional"; public final static string while_start = "while-start"; public final static string while_end = "while-end"; public final static string while_open = "while-open"; public final static string while_close = "while-close"; public final static string while_separator = "while-separator"; public final static string while_index = "while-index"; public final static string while_flag = "while-flag"; public final static string while_list = "while-list"; public final static string whine_flag = "when-flag"; public static final string process_var = "process-var"; public final static string result_flag = "result-flag"; public final static string return_flag = "return-flag"; public final static string console_var = "Console-Var"; public final static string do = "do"; public final static string index = "index"; public final static string条件= "条件"; public final static string name = "name"; public final static string value = "value"; public static final string type = "type"; public static final string format = "format"; public static final string if = "if"; public static final string else = "else"; public final static string file = "file"; public static final string date = "date"; public static final string now = "now"; public static final string decimal = "decimal"; public static final string id = "id"; public static final string params = "params"; public static final string target = "Target"; public static final string single = "single"; public static final string paging = "Paging"; public static final string desc = "desc"; public static final final string break = "break"; public static final string継続= "継続"; public static final string collection = "collection"; public static final string var = "var"; public static final string executor = "executor-1"; public static final string rollback_flag = "rollback-flag"; public static final string service = "service"; public static final string ref = "ref"; public static final string bizs = "bizs"; public static final string titles = "titles"; public static final string columns = "columns"; public static final string curruser = "curruser"; public static final string curreperm = "currperm"; public static final string task_executor = "taskexecutor"; public static final string delimiter = "delimiter"; public static final string opername = "opername"; } currparams.remove(varstr); Currparams.Remove(index); Currparams.Remove(index+"_"); }}POMファイルを添付します
<Project XMLNS = "http://maven.apache.org/pom/4.0.0" xmlns:xsi = "http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation = "http://maven.apach/4.0. http://maven.apache.org/maven-v4_0_0.xsd "> <modelversion> 4.0.0 </modelversion> <groupid> com.rd </groupid> <artifactid> parser </artifactid> <パッケージング> jar </packaging> <url> http://maven.apache.org </url> <dependencies> <依存関係> dom4j </groupid> <artifactid> </artifactid> <バージョン> <バージョン> 2.6.11 </version> </dependency> <dependency> commons-collections </groupId> <artifactid> commons-collections </artifactid> <version> 3.2.1 </version> </depency> <deplency> <groupid> commons-lang </groopid> commons-Lang> <Dependency> groupId> junit </groupId> <artifactid> junit </artifactid> <version> 3.8.1 </version> <scope>テスト</scope> </dependency> </dependency> </dependencies> <build> <resources> <resource> <resource> <resource> <directory> src/main/java < </include> </resource> <resource> <directory> src/main/resources </directory> <cludence> **/*</condult> </resource> </resources> </testresources> <testresources> <testresource> <directory> $ {project.basedir}/src/test/java </directory </testresource> < <Directory> $ {project.basedir}/src/test/resources </directory> </testResource> </testresources> <plugins> <groupid> org.apache.maven.plugins </groupid> <artifactid> maven-compiler-plugin </artifactid> <berversion> 3.1 </versuretid> <Target> 1.8 </Target> <Encoding> UTF-8 </encoding> </configuration> </plugin> </plugins> </build> </project>MyBatis Dynamic SQLを自分で実装する上記の方法は、私があなたと共有したすべてのコンテンツです。参照を提供できることを願っています。wulin.comをもっとサポートできることを願っています。