目次
③concat{'%', #{フィールド名}, '%'}を使用する
I.はじめに
データベースの操作は、今日のソフトウェア開発環境において基本的かつ不可欠なスキルとなっています。データベース関連のタスクを処理する場合、SQL クエリ ステートメントは間違いなく最も一般的に使用されるツールの 1 つです。ただし、アプリケーションの複雑さが増すにつれて、要件を満たすためにより複雑な SQL クエリ ステートメントを作成する必要が生じることがよくあります。これは、今日議論するトピックである MyBatis につながります。
この記事では、MyBatis の 2 つの重要な機能、動的 SQL とファジー クエリについて詳しく説明します。動的 SQL は MyBatis の強力な機能であり、動的に生成された SQL ステートメントを XML マッピング ファイルに記述することができます。ファジー クエリは、大量のデータを処理する場合に一般的なクエリ方法であり、特定の条件を満たすレコードをすばやく見つけるのに役立ちます。
さらに、MyBatis の結果マッピング機能についても説明します。結果マッピングは、データベース クエリの結果を Java オブジェクトにマッピングするプロセスであり、MyBatis の中核機能の 1 つです。結果マッピングを通じて、複雑なデータベース テーブル構造を Java のオブジェクトに変換し、プログラム設計をより直観的かつシンプルにすることができます。
MyBatis のこれらの高度な機能をマスターすると、開発効率が向上するだけでなく、面接で目立つこともできます。したがって、ある程度の経験を持つ開発者であっても、始めたばかりの学習者であっても、時間をかけてこの知識を学び、理解する価値があります。以下の記事では、これらの機能の使い方や注意点などを詳しく紹介していきますので、参考になれば幸いです。
2、MyBatis 動的 SQL
2.1.if要素の使用
以前の記事では、このようなコードを書いたはずです
t_mvc_book を更新します bname=?,btype=?,bprice=?,.... ここで bid=?
フォアグラウンド JSP がパラメータ bname をバックグラウンドに渡さない場合はどうなるでしょうか?
t_mvc_book を更新します。 bname=null、btype='サイエンス フィクション'、bprice=9.9、.... 入札=7
元のフィールド値を変更したくないという理由だけでパラメータを渡さなかったのですが、代わりに null になってしまいました。
パラメータを渡せるのではないかと思う人もいるでしょう。わかりましたが、必須ではありません。Mybatisの動的SQLを使えばこの問題は解決され、SQL文をxmlに変換してifタグで判定することになり、空の場合は判定条件の内容が繋ぎ合わされなくなります。
<update id="updateByPrimaryKeySelective" parameterType="com.csdn.xw.model.Book" >
update t_mvc_book
<set >
<if test="bname != null" >
bname = #{bname,jdbcType=VARCHAR},
</if>
<if test="price != null" >
price = #{price,jdbcType=REAL},
</if>
</set>
where bid = #{bid,jdbcType=INTEGER}
</update>
2.2.foreach要素の使用
別のシナリオを想定してみましょう。コードを書く過程で何かのバッチ削除を書いたはずです。
おそらく、このようなものは配列を反復処理してから、削除メソッドを呼び出します
int id の場合:ids
orderItemsDao.delete(id)
削除する必要がある ID を結合するためのスプライシングである可能性もあります
id in(...) の t_oa_order から削除
mybatis を使用している場合は、次のように書くことができます
<select id="selectByBids" resultType="com.csdn.xw.model.Book" parameterType="java.lang.Integer" >
select
<include refid="Base_Column_List" />
from t_mvc_book
where bid in
<foreach collection="bids" item="bid" open="(" close=")" separator=",">
#{bid}
</foreach>
</select>
今すぐテストしてください
ブックマッパー
List<Book> selectByBids(@Param("bids") List bids);
ブックビズ
List<Book> selectByBids(List bids);
ブックビズインプル
@Override
public List<Book> selectByBids(List bids) {
return bookMapper.selectByBids(bids);
}
デモテストクラス
@Test
public void selectByBids() {
System.out.println("测试的查询的方法");
List<Integer> integers = Arrays.asList(new Integer[]{55, 56, 57, 58});
bookBiz.selectByBids(integers).forEach(System.out::println);
}
試験結果:
3. MyBatis ファジークエリ
エフェクト表示を強調するために、構成ファイル log4j2.xml を追加します。
<?xml version="1.0" encoding="UTF-8"?>
<!-- status : 指定log4j本身的打印日志的级别.ALL< Trace < DEBUG < INFO < WARN < ERROR
< FATAL < OFF。 monitorInterval : 用于指定log4j自动重新配置的监测间隔时间,单位是s,最小是5s. -->
<Configuration status="WARN" monitorInterval="30">
<Properties>
<!-- 配置日志文件输出目录 ${sys:user.home} -->
<Property name="LOG_HOME">/root/workspace/lucenedemo/logs</Property>
<property name="ERROR_LOG_FILE_NAME">/root/workspace/lucenedemo/logs/error</property>
<property name="WARN_LOG_FILE_NAME">/root/workspace/lucenedemo/logs/warn</property>
<property name="PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t-%L] %-5level %logger{36} - %msg%n</property>
</Properties>
<Appenders>
<!--这个输出控制台的配置 -->
<Console name="Console" target="SYSTEM_OUT">
<!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="trace" onMatch="ACCEPT"
onMismatch="DENY" />
<!-- 输出日志的格式 -->
<!-- %d{yyyy-MM-dd HH:mm:ss, SSS} : 日志生产时间 %p : 日志输出格式 %c : logger的名称
%m : 日志内容,即 logger.info("message") %n : 换行符 %C : Java类名 %L : 日志输出所在行数 %M
: 日志输出所在方法名 hostName : 本地机器名 hostAddress : 本地ip地址 -->
<PatternLayout pattern="${PATTERN}" />
</Console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用 -->
<!--append为TRUE表示消息增加到指定文件中,false表示消息覆盖指定的文件内容,默认值是true -->
<File name="log" fileName="logs/test.log" append="false">
<PatternLayout
pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size, 则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
<RollingFile name="RollingFileInfo" fileName="${LOG_HOME}/info.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="info" onMatch="ACCEPT"
onMismatch="DENY" />
<PatternLayout
pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
<Policies>
<!-- 基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour。 modulate=true用来调整时间:比如现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am. -->
<!-- 关键点在于 filePattern后的日期格式,以及TimeBasedTriggeringPolicy的interval, 日期格式精确到哪一位,interval也精确到哪一个单位 -->
<!-- log4j2的按天分日志文件 : info-%d{yyyy-MM-dd}-%i.log -->
<TimeBasedTriggeringPolicy interval="1"
modulate="true" />
<!-- SizeBasedTriggeringPolicy:Policies子节点, 基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小. -->
<!-- <SizeBasedTriggeringPolicy size="2 kB" /> -->
</Policies>
</RollingFile>
<RollingFile name="RollingFileWarn" fileName="${WARN_LOG_FILE_NAME}/warn.log"
filePattern="${WARN_LOG_FILE_NAME}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="warn" onMatch="ACCEPT"
onMismatch="DENY" />
<PatternLayout
pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="2 kB" />
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20" />
</RollingFile>
<RollingFile name="RollingFileError" fileName="${ERROR_LOG_FILE_NAME}/error.log"
filePattern="${ERROR_LOG_FILE_NAME}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd-HH-mm}-%i.log">
<ThresholdFilter level="error" onMatch="ACCEPT"
onMismatch="DENY" />
<PatternLayout
pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
<Policies>
<!-- log4j2的按分钟 分日志文件 : warn-%d{yyyy-MM-dd-HH-mm}-%i.log -->
<TimeBasedTriggeringPolicy interval="1"
modulate="true" />
<!-- <SizeBasedTriggeringPolicy size="10 MB" /> -->
</Policies>
</RollingFile>
</Appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
<Loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息 -->
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<!-- 第三方日志系统 -->
<logger name="org.springframework" level="ERROR" />
<logger name="org.hibernate" level="ERROR" />
<logger name="org.apache.struts2" level="ERROR" />
<logger name="com.opensymphony.xwork2" level="ERROR" />
<logger name="org.jboss" level="ERROR" />
<!-- 配置日志的根节点 -->
<root level="all">
<appender-ref ref="Console" />
<appender-ref ref="RollingFileInfo" />
<appender-ref ref="RollingFileWarn" />
<appender-ref ref="RollingFileError" />
</root>
</Loggers>
</Configuration>
log4j2.xml ファイルは、プロジェクトのクラスパスのルート、src/main/resources ディレクトリに配置する必要があります。
チップ:
Log4j2 の設定ファイルは、ログ出力の形式、レベル、ターゲットなどの情報を設定するために使用されます。Log4j2 は、Log4j 1.x の .properties ファイルの構成方法をサポートしなくなりましたが、json、xml、または jsn を介した構成をサポートします。
具体的には、log4j2.xml ファイルには次のセクションが含まれています。
- 構成: アペンダー、ロガーなどを含む構成情報領域。
- アペンダー: コンソール、ファイル、データベースなどを含む出力対象領域。
- ロガー: パッケージ名、クラス名、ログレベルなどを含むロギング領域。
MyBatis には 3 つのファジー クエリ メソッドがあります。
① #{フィールド名}を使用します
<select id="like1" resultType="com.csdn.xw.model.Book" parameterType="java.lang.String" >
select
<include refid="Base_Column_List" />
from t_mvc_book
where bname like #{bname}
</select>
試験結果:
②${フィールド名}を使用します
<select id="like2" resultType="com.csdn.xw.model.Book" parameterType="java.lang.String" >
select
<include refid="Base_Column_List" />
from t_mvc_book
where bname like ${bname}
</select>
試験結果:
③concat{'%', #{フィールド名}, '%'}を使用する
<select id="like3" resultType="com.csdn.xw.model.Book" parameterType="java.lang.String" >
select
<include refid="Base_Column_List" />
from t_mvc_book
where bname like concat('%',#{bname},'%')
</select>
試験結果:
要約する
これら 3 つのタイプの違いについてもご存知かと思いますが、以下にその違いをまとめておきます。
mybatisの$と#の違い
①$はパラメータを渡すためのプレースホルダ、#はSQLの前処理
②外部形式:$は引用符なしでパラメータを渡します、#は引用符付きです
引用符の有無に大きな違いがあるとは感じたことがないかもしれませんが、それを説明しましょう。
部門番号に従って部門のすべての人事情報を調べたい場合は、$ と # を使用してテストします。
#:select*from t_oa_employee where deptid=#{deptid}
select*from t_oa_employee where deptid='10011'
$:select*from t_oa_employee where deptid=${deptid}
select*from t_oa_employee where deptid=10011
この観点から見ると、実際には違いがないように見えますが、フロントデスクが悪意を持ってパラメータを渡したらどうなるでしょうか?
http://localhost:8080/employee?deptid=10011 または 1=1
そうすれば会社全体の人事情報がバレてしまいます。
③$ passパラメータにSQLインジェクションがある、#が存在しない
④$ を動的カラムとして使用して動的 SQL の開発を完了することができます。
開発プロセス中に、現在のフィールドがニーズを満たしていないことに遭遇したはずです。その後、データベースのフィールドを変更するときに、それが正しければ問題ありませんが、間違っている場合はバービー Q になり、ダイナミック列は、クエリが必要な列として Operate に渡されるパラメータを実現できます。次に例を示します。
user_info という名前のテーブルがあり、そのテーブルには ID、名前、年齢、性別などのフィールドが含まれているとします。次に、渡されたパラメーターに従ってクエリ列を動的に生成する必要があります。次のステートメントを使用できます。
<select id="getUserInfo" resultType="map"> SELECT ${columnName}, ${columnAge}, ${columnGender} FROM user_info </select>
このうち、columnName、columnAge、columnGender はそれぞれクエリ対象の列名を表しており、Java コードで Map 変数を定義し、列名をキーとして使用し、対応する値を値として渡すことができます。例えば
Map<String, Object> columnMap = new HashMap<>(); columnMap.put("columnName", "name"); columnMap.put("columnAge", 18); columnMap.put("columnGender", "男");
次に、columnMap を MyBatis のマッパーに渡して呼び出します。
結局のところ、エディターは依然として $ の代わりに # を使用することを推奨しています。最も一般的に使用されるのは 3 番目です。
4. MyBatis 結果マッピング
MyBatis の使用には複数のシナリオがあり、返される結果は多様です (resultType/resultMap)
① 単一テーブルの対応するエンティティクラスを返すには、クエリ結果は 1 つだけであり、resultType/resultMap を使用できます。
② クエリ結果が複数ある場合、単一のテーブルの対応するエンティティクラスを返すには、resultType/resultMap を使用できます。
③複数のテーブルに対応する結果を返します。クエリ結果は 1 つだけです。通常は resultType または resultMap も使用できます。
④複数のテーブルに対応する結果を返します。複数のクエリ結果があります。通常、resultType も resultMap を使用できます。
⑤ クエリ結果が 1 つしかない場合、単一の列セグメントを返すには、resultType を使用します。
⑥ クエリ結果が複数ある場合に単一の列セグメントを返すには、resultType を使用します。
要約すると、Mybatis での結果セットの処理は 2 つのタイプに分けられます。
resultMap: 戻り値がカスタム エンティティ クラスである場合に適しています
resultType: 戻り値の使用に適したデータ型は非カスタムです。つまり、jdk によって提供される型です。
単一テーブルの場合、resultType と resultMap の両方を使用できます。
1 resultMap を使用して、エンティティ クラスとデータベース フィールド間の関係を指すマッピング関係を返します。
2 resultType を使用して List<T> を返します
3 resultType を使用して単一のオブジェクトを返す
4 resultType を使用して List<Map> を返します [複数テーブルのクエリが返す結果セットに適用されます]
5 resultType を使用して Map<String, Object> を返します [複数テーブルのクエリに適用され、単一の結果セットを返します]
4.1. ケースデモンストレーション
4.1.1.結果マッピングのresultType
次のフィールドを持つユーザー テーブル user があるとします: ID、名前、年齢、性別、電子メール。ここで、これらのフィールドに基づいてユーザー情報をクエリし、User オブジェクトを返す必要があります。
まず、クエリ結果を保存するための User クラスを定義する必要があります。
public class User {
private int id;
private String name;
private int age;
private String gender;
private String email;
// getter和setter方法省略
}
次に、MyBatis のマッパー ファイルで、resultType を使用してクエリ結果を User オブジェクトにマッピングできます。具体的な構成は次のとおりです。
<select id="getUserById" resultType="com.example.User">
SELECT id, name, age, gender, email
FROM user
WHERE id = #{id}
</select>
上記の構成では、 resultType 属性を使用して、クエリ結果がマップされる Java オブジェクトの完全修飾名を指定しました。このようにして、クエリ ステートメントが実行されると、MyBatis はクエリ結果を指定された Java オブジェクトに自動的にマップします。
4.1.2.結果マッピングのresultMap
次のフィールドを含む注文テーブル order があるとします: id、user_id、product_id、price、quantity。ここで、これらのフィールドに基づいて注文情報をクエリし、Order オブジェクトを返す必要があります。
まず、クエリ結果を保存するための Order クラスを定義する必要があります。
public class Order {
private int id;
private int userId;
private int productId;
private double price;
private int quantity;
// getter和setter方法省略
}
次に、MyBatis のマッパー ファイルで、resultMap を使用してクエリ結果を Order オブジェクトにマッピングできます。具体的な構成は次のとおりです。
<resultMap id="OrderResultMap" type="com.example.Order">
<id property="id" column="id" />
<result property="userId" column="user_id" />
<result property="productId" column="product_id" />
<result property="price" column="price" />
<result property="quantity" column="quantity" />
</resultMap>
<select id="getOrderById" resultMap="OrderResultMap">
SELECT id, user_id, product_id, price, quantity
FROM order
WHERE id = #{id}
</select>
上記の構成では、「OrderResultMap」という resultMap を定義しました。その type 属性は、マップされる Java オブジェクトの完全修飾名を指定します。次に、データベース テーブル内の各フィールドに対応する属性名と列名を指定しました。最後に、クエリ ステートメントの resultMap を参照して、クエリ結果を Order オブジェクトにマップします。
クエリ結果のフィールドに Java オブジェクトに対応するプロパティがない場合、そのフィールドは null としてマップされることに注意してください。さらに、クエリ結果のフィールドに Java オブジェクト内に複数の対応するプロパティがある場合、フィールドの値はリストまたは配列にマップされます。
私の共有はここで終わりです。議論やコミュニケーションのためにコメントエリアへようこそ!!
役に立ったと思ったら、高評価をお願いします♥♥