MyBatis - Mybatis の紹介、基本的な使い方、高度な使い方

データベース運用体制の歴史

1.1 JDBC

JDBC (Java Data Base Connection、Java データベース接続) は、SQL ステートメントを実行するための Java API であり、さまざまなリレーショナル データベースへの統合アクセスを提供できます. Java 言語で記述された一連のクラスとインターフェイスで構成されています. JDBC は、Aデータベース開発者がデータベース アプリケーションを作成できるようにするための高レベルのツールとインターフェイスを構築できるベンチマーク

  • 利点: ランタイム: 高速で効率的
  • 短所:編集期間:大量のコード、面倒な例外処理、データベースのクロスプラットフォームをサポートしていない
    ここに画像の説明を挿入

jdbc コア API

  1. DriverManager がデータベースに接続します
  2. データベースに接続するための接続の抽象化
  3. ステートメントは SQL を実行します
  4. ResultSet データの結果セット

1.2 DBUtils

DBUtils は、Java プログラミングでデータベース操作を行うための実用的なツールであり、小さくてシンプルで実用的です。
DBUtils は JDBC の操作をカプセル化し、JDBC 操作を簡素化し、より少ないコードを記述できます。
DBUtils の 3 つのコア機能の紹介

  1. QueryRunner は、SQL ステートメント操作用の API を提供します
  2. 選択操作の後に結果セットをカプセル化する方法を定義するために使用される ResultSetHandler インターフェース
  3. ツールクラスであるDBUtilsクラスはリソースのクローズ方法やトランザクション処理を定義

1.3 休止状態

ORM オブジェクトのリレーショナル マッピング

  1. オブジェクト Java オブジェクト
  2. リレーショナル データ
  3. マッピング マッピング
  • Hibernate は、2001 年に Gavin King によって作成されたオープン ソースのオブジェクト リレーショナル フレームワークです。これは、リレーショナル オブジェクトの永続性とクエリ サービスを備えた Java アプリケーションを構築するための強力で効率的なツールです。
  • Hibernate は、Java クラスをデータベース テーブルに、Java データ型を SQL データ型にマップし、開発者を一般的なデータ永続化プログラミングの 95% から解放します。
  • Hibernate は、O/R マッピング メカニズムとパターンに基づいてこれらのオブジェクトを処理するための、従来の Java オブジェクトとデータベース サーバー間のブリッジです。

ここに画像の説明を挿入
休止状態の利点

  • Hibernate は XML ファイルを使用して、コードを記述することなく Java クラスをデータベース テーブルにマッピングします。
  • Java オブジェクトをデータベースに直接格納および取得するための単純な API を提供します。
  • データベースまたはその他のテーブルに変更がある場合は、XML ファイルの属性のみを変更する必要があります。
  • なじみのない SQL 型を抽象化し、使い慣れた Java オブジェクトが機能するようにします。
  • Hibernate は、動作するためにアプリケーション サーバーを必要としません。
  • データベース内のオブジェクト間の複雑な関係を操作します。
  • 最小化とデータベースへのアクセスのためのスマートな抽出戦略。
  • 単純なデータ クエリを提供します。

休止状態の短所

  • 休止状態の完全なカプセル化により、データの一部の機能を使用できなくなります。
  • ハイバネート キャッシングの問題。
  • Hibernate がコードに結合しすぎています。
  • Hibernate はバグを見つけるのが困難です。
  • Hibernate バッチ データ操作には大量のメモリ領域が必要であり、実行中に必要なオブジェクトが多すぎます

1.4 JDBC テンプレート

JdbcTemplate provides multiple overloaded template methods for data query, and you can choose different template methods such as the needs. クエリが非常に単純な場合は、対応する SQL または関連するパラメーターを渡すだけで、単一の結果が得られます。テンプレートメソッドのセットは、次のように選択できます。

  • 利点: ランタイム: 効率的、Spring フレームワークに組み込まれ、AOP ベースの宣言型トランザクションをサポート
  • 短所: Spring フレームワークと一緒に使用する必要がある、データベースのクロスプラットフォームをサポートしない、デフォルトでキャッシュがない

1.5 私の靴

MyBatis は、カスタム SQL、ストアド プロシージャ、および高度なマッピングをサポートする優れた永続層フレームワーク/半自動 ORM です。MyBatis は、ほぼすべての JDBC コードと、パラメーターを設定して結果セットを取得する作業を排除します。MyBatis は、プリミティブ型、インターフェース、および Java POJO (Plain Old Java Objects、Plain Old Java Objects) を単純な XML または注釈を介してデータベース内のレコードとして構成およびマップできます。

利点:
1. JDBC と比較して、コードの量を 50% 削減します
2. 最も単純な永続化フレームワークで、習得が容易です
3. SQL コードはプログラム コードから完全に分離されており、再利用できます
4. XML タグを提供し、サポートしますダイナミクス SQL の作成
5. マッピング タグの提供、オブジェクトとデータベース間の ORM フィールド関係マッピングのサポート
6. キャッシング、接続プーリング、データベース移植のサポート...

短所:
1. SQL ステートメントの作成の負荷が高く、習熟度が高い
2. データベースの移植性が比較的低い データベースを切り替える必要がある場合、SQL ステートメントは大きく異なります

2 つの MyBatis 構成ファイルの詳細

2.1 MyBatisのログ設定

輸入ポン

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.30</version>
</dependency>

 <dependency>
     <groupId>ch.qos.logback</groupId>
     <artifactId>logback-classic</artifactId>
     <version>1.2.3</version>
 </dependency>

ログバック構成ファイルを追加

<configuration>
    <!--appender 追加器   日志以哪种方式进行输出
            name 取个名字
            class 不同实现类会输出到不同地方
                ch.qos.logback.core.ConsoleAppender 输出到控制台
    -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 格式 -->
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{100} - %msg%n</pattern>
        </encoder>
    </appender>
<!--cn.tulingxueyuan.mapper-->
<!--控制跟细粒度的日志级别  根据包\根据类-->
    <logger name="cn.tulingxueyuan.mapper" level="debug"></logger>
    org.apache.ibatis.transaction
    <!--控制所有的日志级别-->
    <root level="error">
        <!-- 将当前日志级别输出到哪个追加器上面 -->
        <appender-ref ref="STDOUT" />
    </root>
</configuration>
Logger LOGGER= LoggerFactory.getLogger(this.getClass());
/**
 * 日志级别
 * TRACE < DEBUG < INFO < WARN < ERROR。
 * 1        2       3      4       5
 */
@Test
public  void test02(){
    
    
    LOGGER.trace("跟踪级别");
    LOGGER.debug("调试级别");
    LOGGER.info("信息级别");
    LOGGER.warn("警告级别");
    LOGGER.error("异常级别");
}

2.2 mybatis-config.xml グローバル設定ファイルの詳細説明

mybatis プロジェクトで、mybatis-config.xml 構成ファイルが見つかりました.この構成ファイルは、mybatis のグローバル構成ファイルであり、関連するグローバル構成と、任意の操作で有効になる構成に使用されます。以下に、属性の詳細な説明を示します。これにより、その後の使用により熟練できるようになります。

公式説明:
MyBatis 構成ファイルには、MyBatis の動作に深く影響する設定とプロパティ情報が含まれています。構成ドキュメントのトップレベルの構造は次のとおりです。

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--引入外部配置文件,类似于Spring中的property-placeholder
    resource:从类路径引入
    url:从磁盘路径或者网络路径引入
    -->
    <properties resource="db.properties"></properties>
    <!--用来控制mybatis运行时的行为,是mybatis中的重要配置-->
    <settings>
        <!--设置列名映射的时候是否是驼峰标识-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--typeAliases表示为我们引用的实体类起别名,默认情况下我们需要写类的完全限定名
    如果在此处做了配置,那么可以直接写类的名称,在type中配置上类的完全限定名,在使用的时候可以忽略大小写
    还可以通过alias属性来表示类的别名
    -->
    <typeAliases>
<!--        <typeAlias type="cn.tulingxueyuan.bean.Emp" alias="Emp"></typeAlias>-->
        <!--如果需要引用多个类,那么给每一个类起别名肯定会很麻烦,因此可以指定对应的包名,那么默认用的是类名-->
        <package name="cn.tulingxueyuan.bean"/>
    </typeAliases>
    <!--
    在实际的开发过程中,我们可能分为开发环境,生产环境,测试环境等等,每个环境的配置可以是不一样的
    environment就用来表示不同环境的细节配置,每一个环境中都需要一个事务管理器以及数据源的配置
    我们在后续的项目开发中几乎都是使用spring中配置的数据源和事务管理器来配置,此处不需要研究
    -->
    <!--default:用来选择需要的环境-->
    <environments default="development">
        <!--id:表示不同环境的名称-->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!--配置数据库连接-->
            <dataSource type="POOLED">
                <!--使用${}来引入外部变量-->
                <property name="driver" value="${driverClassname}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--
    在不同的数据库中,可能sql语句的写法是不一样的,为了增强移植性,可以提供不同数据库的操作实现
    在编写不同的sql语句的时候,可以指定databaseId属性来标识当前sql语句可以运行在哪个数据库中
    -->
    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql"/>
        <property name="SQL Server" value="sqlserver"/>
        <property name="Oracle" value="orcl"/>
    </databaseIdProvider>
    
    <!--将sql的映射文件适用mappers进行映射-->
    <mappers>
        <!--
        指定具体的不同的配置文件
        class:直接引入接口的全类名,可以将xml文件放在dao的同级目录下,并且设置相同的文件名称,同时可以使用注解的方式来进行相关的配置
        url:可以从磁盘或者网络路径查找sql映射文件
        resource:在类路径下寻找sql映射文件
        -->
<!--        <mapper resource="EmpDao.xml"/>
        <mapper resource="UserDao.xml"/>
        <mapper class="cn.tulingxueyuan.dao.EmpDaoAnnotation"></mapper>-->
        <!--
        当包含多个配置文件或者配置类的时候,可以使用批量注册的功能,也就是引入对应的包,而不是具体的配置文件或者类
        但是需要注意的是,
        1、如果使用的配置文件的形式,必须要将配置文件跟dao类放在一起,这样才能找到对应的配置文件.
            如果是maven的项目的话,还需要添加以下配置,原因是maven在编译的文件的时候只会编译java文件
                <build>
                    <resources>
                        <resource>
                            <directory>src/main/java</directory>
                        <includes>
                            <include>**/*.xml</include>
                        </includes>
                    </resource>
                    </resources>
                </build>

        2、将配置文件在resources资源路径下创建跟dao相同的包名
        -->
        <package name="cn.tulingxueyuan.dao"/>
    </mappers>
</configuration>

2.3 Mybatis SQL マッピングファイルの詳細説明

MyBatis の真の力は、その魔法が存在するステートメント マッピングにあります。その強力さゆえに、マッパーの XML ファイルは比較的単純です。これを同じ機能を持つ JDBC コードと比較すると、コードのほぼ 95% が節約されていることがすぐにわかります。MyBatis は、ユーザーが SQL コードにより集中できるように、使用コストの削減に取り組んでいます。
SQL マッピング ファイルには、最上位の要素がいくつかあります (定義する順序でリストされています)。

  • cache – この名前空間のキャッシュ構成。
  • cache-ref – 他の名前空間からキャッシュ構成を参照します。
  • resultMap – データベースの結果セットからオブジェクトをロードする方法を説明する、最も複雑で強力な要素です。
  • parameterMap – 古いスタイルのパラメーター マップ。この要素は非推奨であり、将来削除される可能性があります! インライン パラメータ マッピングを使用してください。この要素はドキュメントには記載されていません。
  • sql – 他のステートメントから参照できる、再利用可能なステートメントのブロック。
  • insert – insert ステートメントをマップします。
  • update – マップ更新ステートメント。
  • delete – delete ステートメントをマップします。
  • select - マップ クエリ ステートメント。

多くの属性を各最上位要素タグに追加できます.特定の構成を詳細に理解することから始めましょう.

insert、update、delete元素

属性 説明
ID このステートメントを参照するために使用できる名前空間内の一意の識別子。
パラメータの種類 このステートメントに渡されるパラメーターの完全修飾クラス名または別名。MyBatis は型ハンドラー (TypeHandler) を介して特定の着信ステートメントのパラメーターを推測でき、デフォルト値は未設定 (unset) であるため、この属性はオプションです。
パラメータマップ 外部 parameterMap を参照するために使用される属性で、現在非推奨です。代わりに、インライン パラメータ マップと parameterType 属性を使用してください。
フラッシュキャッシュ true に設定した後、ステートメントが呼び出される限り、ローカル キャッシュと第 2 レベルのキャッシュがクリアされます。デフォルト値: (insert、update、delete ステートメントの場合) true。
タイムアウト この設定は、例外をスローする前にデータベースが要求の結果を返すのをドライバーが待機する秒数です。デフォルトは未設定です (データベース ドライバによって異なります)。
ステートメントタイプ オプションの STATEMENT、PREPARED または CALLABLE。これにより、MyBatis はそれぞれ Statement、PreparedStatement、または CallableStatement を使用するようになります。デフォルト値は PREPARED です。
useGeneratedKeys (挿入と更新にのみ適用可能) これにより、MyBatis は JDBC の getGeneratedKeys メソッドを使用して、データベースによって内部的に生成された主キーを取得します (例: MySQL や SQL Server などのリレーショナル データベース管理システムの自動インクリメント フィールド)。デフォルト値: false .
キープロパティ (挿入と更新にのみ適用) オブジェクトを一意に識別できる属性を指定します. MyBatis は getGeneratedKeys の戻り値または insert ステートメントの selectKey サブ要素を使用してその値を設定します. デフォルト値: 未設定 (未設定). 生成された列が複数ある場合は、複数のプロパティ名をコンマで区切ることができます。
キー列 (挿入と更新にのみ適用可能) キー値を生成するためにテーブルに列名を設定します. 一部のデータベース (PostgreSQL など) では、主キー列がテーブルの最初の列でない場合、設定する必要があります. 生成された列が複数ある場合は、複数のプロパティ名をコンマで区切ることができます。
データベース ID データベース ベンダー識別子 (databaseIdProvider) が構成されている場合、MyBatis は、databaseId のない、または現在の databaseId と一致するすべてのステートメントを読み込みます; 含むステートメントと含まないステートメントの両方がある場合、含まないステートメントは無視されます。
 <!--如果数据库支持自增可以使用这样的方式-->
 <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into user(user_name) values(#{userName})
 </insert>
 <!--如果数据库不支持自增的话,那么可以使用如下的方式进行赋值查询-->
 <insert id="insertUser2" >
     <selectKey order="BEFORE" keyProperty="id" resultType="integer">
        select max(id)+1 from user
     </selectKey>
    insert into user(id,user_name) values(#{id},#{userName})
 </insert>

詳細については、以下の第 3 章を参照してください

MyBatisの3つのXMLベースの詳細な使用 - パラメータ、戻り結果の処理

3.1 パラメータの値を取得する方法

xml ファイルに sql ステートメントを記述する場合、値を取得する方法は 2 つあります。つまり、#{} と ${} です。これらの違いを見てみましょう。

<!--获取参数的方式:
    1.#{} ==> jdbc String sql=" SELECT id,user_name FROM EMP WHERE id=?"
        1.会经过JDBC当中PreparedStatement的预编译,会根据不同的数据类型来编译成对应数据库所对应的数据。
        2.能够有效的防止SQL注入。 推荐使用!!
        特殊用法:
        自带很多内置参数的属性:通常不会使用。了解
        javaType、jdbcType、mode、numericScale、resultMap、typeHandler.
        比如 需要改变默认的NULL===>OTHER:#{id,javaType=NULL}
             想保留小数点后两位:#{id,numericScale=2}

    2.${} ==> jdbc String sql=" SELECT id,user_name FROM EMP WHERE id="+id
        1.不会进行预编译,会直接将输入进来的数据拼接在SQL中。
        2.存在SQL注入的风险。不推荐使用。
        特殊用法:
            1.调试情况下可以临时使用。
            2.实现一些特殊功能:前提一定要保证数据的安全性。
             比如:动态表、动态列. 动态SQL.
-->
<select id="SelectEmp"  resultType="Emp"  resultMap="emp_map"  >
    SELECT id,user_name,create_date FROM EMP where id=#{id}
</select>

3.2 パラメータ転送の選択

<!--
   参数传递的处理:
   1.单个参数:SelectEmp(Integer id);
       mybatis 不会做任何特殊要求
       获取方式:
           #:{输入任何字符获取参数}

   2.多个参数:Emp SelectEmp(Integer id,String username);
       mybatis 会进行封装
       会将传进来的参数封装成map:
       1个值就会对应2个map项 :  id===>  {key:arg0 ,value:id的值},{key:param1 ,value:id的值}
                                 username===>  {key:arg1 ,value:id的值},{key:param2 ,value:id的值}
       获取方式:
            没使用了@Param:
                      id=====>  #{arg0} 或者 #{param1}
                username=====>  #{arg1} 或者 #{param2}
            除了使用这种方式还有别的方式,因为这种方式参数名没有意义:
            设置参数的别名:@Param(""):SelectEmp(@Param("id") Integer id,@Param("username") String username);
            当使用了@Param:
                      id=====>  #{id} 或者 #{param1}
                username=====>  #{username} 或者 #{param2}

    3. javaBean的参数:
       单个参数:Emp SelectEmp(Emp emp);
       获取方式:可以直接使用属性名
           emp.id=====>#{id}
           emp.username=====>#{username}
       多个参数:Emp SelectEmp(Integer num,Emp emp);
           num===>    #{param1} 或者 @Param
           emp===> 必须加上对象别名: emp.id===> #{param2.id} 或者  @Param("emp")Emp emp    ====>#{emp.id}
                                   emp.username===> #{param2.username} 或者  @Param("emp")Emp emp    ====>#{emp.username}
    4.集合或者数组参数:
           Emp SelectEmp(List<String> usernames);
       如果是list,MyBatis会自动封装为map:
           {key:"list":value:usernames}
             没用@Param("")要获得:usernames.get(0)  =====>  #{list[0]}
                                 :usernames.get(0)  =====>  #{agr0[0]}
             有@Param("usernames")要获得:usernames.get(0)  =====>  #{usernames[0]}
                                        :usernames.get(0)  =====>  #{param1[0]}
       如果是数组,MyBatis会自动封装为map:
           {key:"array":value:usernames}
             没用@Param("")要获得:usernames.get(0)  =====>  #{array[0]}
                               :usernames.get(0)  =====>  #{agr0[0]}
             有@Param("usernames")要获得:usernames.get(0)  =====>  #{usernames[0]}
                                        :usernames.get(0)  =====>  #{param1[0]}
     5.map参数
       和javaBean的参数传递是一样。
       一般情况下:
           请求进来的参数 和pojo对应,就用pojo
           请求进来的参数 没有和pojo对应,就用map
           请求进来的参数 没有和pojo对应上,但是使用频率很高,就用TO、DTO(就是单独为这些参数创建一个对应的javaBean出来,使参数传递更规范、更重用)

   --> 

   <!--
   接口:SelectEmp(String username,@Param("id") Integer id);
          username====>  #{arg0}  #{param1}
          id====>  #{id}  #{param2}
   接口:SelectEmp(@Param("beginDate") String beginDate,
                    String endDate,
                   Emp emp);
          beginDate====>  #{beginDate}  #{param1}
          endDate====>  #{arg1}  #{param2}
          emp.id====>#{arg2.id}  #{param2.id}
   接口:SelectEmp(List<Integer> ids,
                  String[] usernames,
                  @Param("beginDate") String beginDate,
                    String endDate,);
               ids.get(0)=====> #{list[0]}      #{param1[0]}
               usernames[0]=====> #{array[0]}      #{param2[0]}
          beginDate====>  #{beginDate}  #{param3}
          end====>  #{arg3}  #{param4}
   -->

3.3 コレクションの返却結果の処理

EmpDao.xml

<!--当返回值的结果是集合的时候,返回值的类型依然写的是集合中具体的类型-->
    <select id="selectAllEmp" resultType="cn.tulingxueyuan.bean.Emp">
        select  * from emp
    </select>
<!--在查询的时候可以设置返回值的类型为map,当mybatis查询完成之后会把列的名称作为key
    列的值作为value,转换到map中
    -->
    <select id="selectEmpByEmpReturnMap" resultType="map">
        select * from emp where empno = #{empno}
    </select>

    <!--注意,当返回的结果是一个集合对象的时候,返回值的类型一定要写集合具体value的类型,
    同时在dao的方法上要添加@MapKey的注解,来设置key是什么结果
    @MapKey("empno")
    Map<Integer,Emp> getAllEmpReturnMap();-->
    <select id="getAllEmpReturnMap" resultType="cn.tulingxueyuan.bean.Emp">
        select * from emp
    </select>

3.4 カスタム結果セット—resultMap

<!--1.声明resultMap自定义结果集   resultType 和 resultMap 只能使用一个。
    id 唯一标识, 需要和<select 上的resultMap 进行对应
    type 需要映射的pojo对象, 可以设置别名
    autoMapping 自动映射,(默认=true) 只要字段名和属性名遵循映射规则就可以自动映射,但是不建议,哪怕属性名和字段名一一对应上了也要显示的配置映射
    extends  如果多个resultMap有重复映射,可以声明父resultMap,将公共的映射提取出来, 可以减少子resultMap的映射冗余
-->
<resultMap id="emp_map" type="emp" autoMapping="false" extends="common_map">
    <result column="create_date" property="cjsj"></result>
</resultMap>

<resultMap id="common_map" type="emp" autoMapping="false" >
    <!-- <id> 主键必须使用  对底层存储有性能作用
                 column  需要映射的数据库字段名
                 property 需要映射的pojo属性名
     -->
    <id column="id" property="id"></id>
    <result column="user_name" property="username"></result>
</resultMap>

<!--2.使用resultMap 关联 自定义结果集的id-->
<select id="SelectEmp"  resultType="Emp"  resultMap="emp_map"  >
    SELECT id,user_name,create_date FROM EMP where id=#{id}
</select>

MyBatis の 4 つの XML ベースの詳細な使用方法 - 高度な結果マッピング

4.1 共同クエリ

emp.java

import java.time.LocalDate;

public class Emp {
    
    
    private Integer id;
    private String username;
    private LocalDate createDate;
    private deptId deptId;


    public Integer getId() {
    
    
        return id;
    }

    public void setId(Integer id) {
    
    
        this.id = id;
    }

    public String getUsername() {
    
    
        return username;
    }

    public void setUsername(String username) {
    
    
        this.username = username;
    }

    public LocalDate getCreateDate() {
    
    
        return createDate;
    }

    public void setCreateDate(LocalDate createDate) {
    
    
        this.createDate = createDate;
    }

    public Integer getDeptId() {
    
    
        return dept;
    }

    public void setDeptId(Integer deptId) {
    
    
        this.dept = dept;
    }

    @Override
    public String toString() {
    
    
        return "Emp{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", createDate=" + createDate +
                ", deptId=" + deptId+
                '}';
    }
}

EmpMapper.xml

<!-- 实现表联结查询的方式:  可以映射: DTO -->
<resultMap id="QueryEmp_Map" type="QueryEmpDTO">
    <id column="e_id" property="id"></id>
    <result column="user_name" property="username"></result>
    <result column="d_id" property="deptId"></result>
    <result column="dept_name" property="deptName"></result>
</resultMap>

<select id="QueryEmp"  resultMap="QueryEmp_Map">
    select t1.id as e_id,t1.user_name,t2.id as d_id,t2.dept_name from emp t1
    INNER JOIN dept t2 on t1.dept_id=t2.id
    where t1.id=#{id}
</select>

<!-- 实现表联结查询的方式:  可以映射map -->
<resultMap id="QueryEmp_Map" type="map">
    <id column="e_id" property="id"></id>
    <result column="user_name" property="username"></result>
    <result column="d_id" property="deptId"></result>
    <result column="dept_name" property="deptName"></result>
</resultMap>

<select id="QueryEmp"  resultMap="QueryEmp_Map">
    select t1.id as e_id,t1.user_name,t2.id as d_id,t2.dept_name from emp t1
    INNER JOIN dept t2 on t1.dept_id=t2.id
    where t1.id=#{id}
</select>

QueryEmpDTO

public class QueryEmpDTO {
    
    

    private String deptName;
    private Integer deptId;
    private Integer id;
    private String username;

    public String getDeptName() {
    
    
        return deptName;
    }

    public void setDeptName(String deptName) {
    
    
        this.deptName = deptName;
    }

    public Integer getDeptId() {
    
    
        return deptId;
    }

    public void setDeptId(Integer deptId) {
    
    
        this.deptId = deptId;
    }

    public Integer getId() {
    
    
        return id;
    }

    public void setId(Integer id) {
    
    
        this.id = id;
    }

    public String getUsername() {
    
    
        return username;
    }

    public void setUsername(String username) {
    
    
        this.username = username;
    }

    @Override
    public String toString() {
    
    
        return "QueryEmpDTO{" +
                "deptName='" + deptName + '\'' +
                ", deptId=" + deptId +
                ", id=" + id +
                ", username='" + username + '\'' +
                '}';
    }
}

テスト

@Test
public void test01() {
    
    
    try(SqlSession sqlSession = sqlSessionFactory.openSession()){
    
    
        // Mybatis在getMapper就会给我们创建jdk动态代理
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        QueryEmpDTO dto = mapper.QueryEmp(4);
        System.out.println(dto);
    }
}

4.2 ネストされた結果

4.2.1 多対一

EmpMapper.xml

<!--嵌套结果   多 对 一  -->
<resultMap id="QueryEmp_Map2" type="Emp">
    <id column="e_id" property="id"></id>
    <result column="user_name" property="username"></result>
    <!--
    association 实现多对一中的  “一”
        property 指定对象中的嵌套对象属性
    -->
    <association property="dept">
        <id column="d_id" property="id"></id>
        <id column="dept_name" property="deptName"></id>
    </association>
</resultMap>

<select id="QueryEmp2"  resultMap="QueryEmp_Map2">
    select t1.id as e_id,t1.user_name,t2.id as d_id,t2.dept_name from emp t1
    INNER JOIN dept t2 on t1.dept_id=t2.id
    where t1.id=#{id}
</select>

4.2.2 一対多

<!-- 嵌套结果: 一对多  查询部门及所有员工 -->
<resultMap id="SelectDeptAndEmpsMap" type="Dept">
    <id column="d_id"  property="id"></id>
    <id column="dept_name"  property="deptName"></id>
    <!--
    <collection  映射一对多中的 “多”
        property 指定需要映射的“多”的属性,一般声明为List
        ofType  需要指定list的类型
    -->
    <collection property="emps" ofType="Emp" >
        <id column="e_id" property="id"></id>
        <result column="user_name" property="username"></result>
        <result column="create_date" property="createDate"></result>
    </collection>
</resultMap>

<select id="SelectDeptAndEmps" resultMap="SelectDeptAndEmpsMap">
    select t1.id as d_id,t1.dept_name,t2.id e_id,t2.user_name,t2.create_date from dept t1
    LEFT JOIN emp t2 on t1.id=t2.dept_id
    where t1.id=#{id}
</select>

Emp.java

import java.time.LocalDate;

public class Emp {
    
    
    private Integer id;
    private String username;
    private LocalDate createDate;
    private Dept dept;


    public Integer getId() {
    
    
        return id;
    }

    public void setId(Integer id) {
    
    
        this.id = id;
    }

    public String getUsername() {
    
    
        return username;
    }

    public void setUsername(String username) {
    
    
        this.username = username;
    }

    public LocalDate getCreateDate() {
    
    
        return createDate;
    }

    public void setCreateDate(LocalDate createDate) {
    
    
        this.createDate = createDate;
    }

    public Dept getDept() {
    
    
        return dept;
    }

    public void setDept(Dept dept) {
    
    
        this.dept = dept;
    }

    @Override
    public String toString() {
    
    
        return "Emp{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", createDate=" + createDate +
                ", dept=" + dept +
                '}';
    }
}

部門.java:

public class Dept {
    
    
    private Integer id;
    private String deptName;
    private List<Emp> emps;

    public Integer getId() {
    
    
        return id;
    }

    public void setId(Integer id) {
    
    
        this.id = id;
    }

    public String getDeptName() {
    
    
        return deptName;
    }

    public void setDeptName(String deptName) {
    
    
        this.deptName = deptName;
    }

    public List<Emp> getEmps() {
    
    
        return emps;
    }

    public void setEmps(List<Emp> emps) {
    
    
        this.emps = emps;
    }


    @Override
    public String toString() {
    
    
        return "Dept{" +
                "id=" + id +
                ", deptName='" + deptName + '\'' +
                ", emps=" + emps +
                '}';
    }
}

EmpMapper.java:

public interface EmpMapper {
    
    

    /*徐庶老师实际开发中的实现方式*/
    QueryEmpDTO QueryEmp(Integer id);

    /*实用嵌套结果实现联合查询  多对一 */
    Emp QueryEmp2(Integer id);


    /*实用嵌套查询实现联合查询  多对一 */
    Emp QueryEmp3(Integer id);
}

DeptMapper.java:

public interface DeptMapper {
    
    
    //嵌套查询: 一对多   使用部门id查询员工
   Dept SelectDeptAndEmps(Integer id);

   // 嵌套查询(异步查询): 一对多  查询部门及所有员工
    Dept SelectDeptAndEmps2(Integer id);
}

4.3 ネストされたクエリ

上記の論理クエリでは、SQL文の連想クエリを自力で完成させているのですが、mybatisに自動連想クエリを実現してもらえないでしょうか?

4.3.1 多対一

EmpMapper.xml:

<!--嵌套查询(分步查询)   多 对 一
  联合查询和分步查询区别:   性能区别不大
                            分部查询支持 懒加载(延迟加载)
   需要设置懒加载,一定要使用嵌套查询的。
   要启动懒加载可以在全局配置文件中设置 lazyLoadingEnabled=true
   还可以单独为某个分步查询设置立即加载 <association fetchType="eager"
  -->
<resultMap id="QueryEmp_Map3" type="Emp">
    <id column="id" property="id"></id>
    <result column="user_name" property="username"></result>
    <!-- association 实现多对一中的  “一”
        property 指定对象中的嵌套对象属性
        column  指定将哪个字段传到分步查询中
        select 指定分步查询的 命名空间+ID
        以上3个属性是实现分步查询必须的属性
        fetchType 可选, eager|lazy   eager立即加载   lazy跟随全局配置文件中的lazyLoadingEnabled
     -->
    <association property="dept"    column="dept_id"  select="cn.tulingxueyuan.mapper.DeptMapper.SelectDept">
    </association>
</resultMap>

<select id="QueryEmp3"  resultMap="QueryEmp_Map3">
   select  * from emp where id=#{id}
</select>

DeptMapper.xml

<!-- 根据部门id查询部门-->
<select id="SelectDept" resultType="dept">
    SELECT * FROM dept where id=#{id}
</select>

4.3.2 一対多

DeptMapper.xml

<!-- 嵌套查询(异步查询): 一对多  查询部门及所有员工 -->
<resultMap id="SelectDeptAndEmpsMap2" type="Dept">
    <id column="d_id"  property="id"></id>
    <id column="dept_name"  property="deptName"></id>
    <!--
    <collection  映射一对多中的 “多”
        property 指定需要映射的“多”的属性,一般声明为List
        ofType  需要指定list的类型
        column 需要将当前查询的字段传递到异步查询的参数
        select 指定异步查询
    -->
    <collection property="emps" ofType="Emp" column="id" select="cn.tulingxueyuan.mapper.EmpMapper.SelectEmpByDeptId" >
    </collection>
</resultMap>

<select id="SelectDeptAndEmps2" resultMap="SelectDeptAndEmpsMap2">
    SELECT * FROM dept where id=#{id}
</select>

EmpMapper.xml

<!-- 根据部门id所有员工 -->
<select id="SelectEmpByDeptId"  resultType="emp">
    select  * from emp where dept_id=#{id}
</select>

Emp.java

public class Emp {
    
    
    private Integer id;
    private String username;
    private LocalDate createDate;
    private Dept dept;


    public Integer getId() {
    
    
        return id;
    }

    public void setId(Integer id) {
    
    
        this.id = id;
    }

    public String getUsername() {
    
    
        return username;
    }

    public void setUsername(String username) {
    
    
        this.username = username;
    }

    public LocalDate getCreateDate() {
    
    
        return createDate;
    }

    public void setCreateDate(LocalDate createDate) {
    
    
        this.createDate = createDate;
    }

    public Dept getDept() {
    
    
        return dept;
    }

    public void setDept(Dept dept) {
    
    
        this.dept = dept;
    }

    @Override
    public String toString() {
    
    
        return "Emp{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", createDate=" + createDate +
                ", dept=" + dept +
                '}';
    }

}

部門.java:

public class Dept {
    
    
    private Integer id;
    private String deptName;
    private List<Emp> emps;

    public Integer getId() {
    
    
        return id;
    }

    public void setId(Integer id) {
    
    
        this.id = id;
    }

    public String getDeptName() {
    
    
        return deptName;
    }

    public void setDeptName(String deptName) {
    
    
        this.deptName = deptName;
    }

    public List<Emp> getEmps() {
    
    
        return emps;
    }

    public void setEmps(List<Emp> emps) {
    
    
        this.emps = emps;
    }


    @Override
    public String toString() {
    
    
        return "Dept{" +
                "id=" + id +
                ", deptName='" + deptName + '\'' +
                ", emps=" + emps +
                '}';
    }
}

EmpMapper.java:

public interface EmpMapper {
    
    

    /*徐庶老师实际开发中的实现方式*/
    QueryEmpDTO QueryEmp(Integer id);

    /*实用嵌套结果实现联合查询  多对一 */
    Emp QueryEmp2(Integer id);


    /*实用嵌套查询实现联合查询  多对一 */
    Emp QueryEmp3(Integer id);
}

DeptMapper.java:

public interface DeptMapper {
    
    
    //嵌套查询: 一对多   使用部门id查询员工
   Dept SelectDeptAndEmps(Integer id);

   // 嵌套查询(异步查询): 一对多  查询部门及所有员工
    Dept SelectDeptAndEmps2(Integer id);
}

4.4 遅延クエリ

テーブルの関連付けを行っている場合、結果をクエリするときに関連付けられたオブジェクトの属性値 (select count(1)...) が必要ない可能性があるため、このとき遅延読み込みによって関数を実現できます。
次のプロパティmybatis-config.xml をグローバル構成ファイルに追加します

<!-- 开启延迟加载,所有分步查询都是懒加载 (默认是立即加载)-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--当开启式, 使用pojo中任意属性都会加载延迟查询 ,默认是false
<setting name="aggressiveLazyLoading" value="false"/>-->
<!--设置对象的哪些方法调用会加载延迟查询   默认:equals,clone,hashCode,toString-->
<setting name="lazyLoadTriggerMethods" value=""/>

グローバル ローディングが設定されているが、特定の SQL ステートメントを照会するときに遅延戦略を使用したくない場合は、fetchType の下に属性を追加できます。

<association property="dept" fetchType="eager"  column="dept_id"  select="cn.tulingxueyuan.mapper.DeptMapper.SelectDept">
</association>

4.5 まとめ

ここに画像の説明を挿入
3 種類のリレーションシップに関連するクエリには 2 つの方法があります: ネストされたクエリ、ネストされた結果
Mybatis の遅延読み込み構成、次のコードをグローバル構成ファイルに追加します。

<settings>
	<setting name=”lazyLoadingEnabled” value=”true” />
	<setting name=”aggressiveLazyLoading” value=”false”/>
</settings>

マッピングファイルでは、要素と要素の両方にデフォルトで遅延読み込み属性、つまりデフォルト属性 fetchType="lazy" (属性 fetchType="eager" は即時読み込みを意味する) が設定されているため、遅延読み込みを有効にした後に構成ファイルにロードするため、マッピング ファイルで構成する必要はありません

1 対 1
要素を使用して 1 対 1 の関連付けマッピングを実行するのは非常に簡単です. 次の 2 つの構成例を参照するだけで済みます. 1 対多の要素にはサブ要素が含まれており、MyBatis はこれを
ここに画像の説明を挿入
使用
<resultMap><collection>ます
<collection>1 対多関連関係のサブ要素を処理するための要素. 要素のほとんどの属性<association>は要素と同じですが、特殊な属性も含まれています - ofType
ofType 属性は javaType 属性に対応します。エンティティ オブジェクトのコレクション クラス属性に含まれる要素タイプを指定するために使用されます。要素の使用も非常に簡単です. 構成については、次
<collection >の 2 つの例を参照することもできます. 具体的なコードは次のとおりです:使い方と一対多の関係クエリ文の使い方は基本的に同じです)
ここに画像の説明を挿入

5 つの動的 SQL

動的 SQL は、MyBatis の強力な機能の 1 つです。JDBC などのフレームワークを使用したことがある方なら、SQL ステートメントを条件ごとに連結するのがいかに大変かを理解できるはずです.たとえば、連結する場合は、必要なスペースを追加することを忘れないでください。また、コンマを削除するように注意してください.リストの最後の列名。動的 SQL を使用すると、この問題を完全に取り除くことができます。
動的 SQL の操作は簡単な作業ではありませんが、MyBatis は、SQL マップされたステートメントで使用できる強力な動的 SQL 言語により、この機能の使いやすさを大幅に向上させます。
以前に JSTL や XML に似た言語ベースのテキスト プロセッサを使用したことがある場合は、動的 SQL 要素になじみがあるかもしれません。以前のバージョンの MyBatis では、多数の要素を理解するのに時間がかかりました。MyBatis 3 では、強力な OGNL ベースの式を使用して、以前の要素のほとんどが置き換えられ、要素の種類が大幅に簡素化され、学習する要素の種類が元の半分以下になりました。

  • もしも
  • 選択する(いつ、そうでなければ)
  • トリム (場所、設定)
  • foreach
  • 練る
  • SQLフラグメント

5.1 もし

EmpDao.xml

<select id="getEmpByCondition" resultType="cn.tulingxueyuan.bean.Emp">
    select * from emp where 
    <if test="empno!=null">
        empno = #{empno} and
    </if>
    <if test="ename!=null">
        ename like #{ename} and
    </if>
    <if test="sal!=null">
        sal > #{sal}
    </if>
</select>

上記のコードは正常に見えますが、渡すパラメーター値が欠落している場合に注意する必要があります。このとき、連結されたSQL文が問題になります.たとえば、パラメータが渡されなかったり、最後のパラメータが失われたりすると、ステートメントにwhereまたはandの追加のキーワードが含まれます.したがって、具体的な解決策もmybatisで与えられた:

どこ

where 要素は、子要素が何も返さない場合にのみ「WHERE」句を挿入します。また、句が「AND」または「OR」で始まる場合、where 要素によってそれらも削除されます。

<select id="getEmpByCondition" resultType="cn.tulingxueyuan.bean.Emp">
    select * from emp
    <where>
        <if test="empno!=null">
            empno = #{empno}
        </if>
        <if test="ename!=null">
            and ename like #{ename}
        </if>
        <if test="sal!=null">
            and sal > #{sal}
        </if>
    </where>
</select>

今のところ問題ないようですが、splicing sql文の前後に条件が追加されているので、どのように対処すればよいでしょうか?
トリム

 <!--
 trim截取字符串:
 prefix:前缀,为sql整体添加一个前缀
 prefixOverrides:去除整体字符串前面多余的字符
 suffixOverrides:去除后面多余的字符串
 -->
 <select id="getEmpByCondition" resultType="cn.tulingxueyuan.bean.Emp">
     select * from emp

     <trim prefix="where" prefixOverrides="and" suffixOverrides="and">
         <if test="empno!=null">
             empno = #{empno} and
         </if>
         <if test="ename!=null">
             ename like #{ename} and
         </if>
         <if test="sal!=null">
             sal > #{sal} and
         </if>
     </trim>
 </select>

5.2 foreach

動的 SQL のもう 1 つの一般的な使用例は、コレクションのトラバースです (特に IN 条件文を構築する場合)。

 <!--foreach是对集合进行遍历
	 collection="deptnos" 指定要遍历的集合
	 close="" 表示以什么结束
	 index="" 给定一个索引值
	 item="" 遍历的每一个元素的值
	 open="" 表示以什么开始
	 separator="" 表示多个元素的分隔符
 -->
 <select id="getEmpByDeptnos" resultType="Emp">
    select * from emp where deptno in
     <foreach collection="deptnos" close=")" index="idx" item="deptno" open="(" separator=",">
        #{deptno}
     </foreach>
 </select>

5.3 選択、いつ、そうでなければ

場合によっては、すべての条件を使用するのではなく、複数の条件から 1 つを選択したいことがあります。この状況のた​​めに、MyBatis は choose 要素を提供します。これは、Java の switch ステートメントに少し似ています。

<select id="getEmpByConditionChoose" resultType="cn.tulingxueyuan.bean.Emp">
        select * from emp
     <where>
         <choose>
             <when test="empno!=null">
                 empno > #{empno}
             </when>
             <when test="ename!=null">
                 ename like #{ename}
             </when>
             <when test="sal!=null">
                 sal > #{sal}
             </when>
             <otherwise>
                 1=1
             </otherwise>
         </choose>
     </where>
 </select>

5.4セット

動的更新ステートメントの同様のソリューションは、set と呼ばれます。set 要素を使用すると、更新が必要な列を動的に含めることができ、更新されない他の列は無視されます。

<update id="updateEmpByEmpno">
  update emp
   <set>
       <if test="empno!=null">
          empno=#{empno},
       </if>
       <if test="ename!=null">
          ename = #{ename},
       </if>
       <if test="sal!=null">
          sal = #{sal}
       </if>
   </set>
   <where>
      empno = #{empno}
   </where>
</update>

5.5バインド

bind 要素を使用すると、OGNL 式の外部で変数を作成し、それを現在のコンテキストにバインドできます。例えば:

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

5.6 SQL

この要素を使用して、他のステートメントで使用する SQL コードの再利用可能なフラグメントを定義できます。パラメーターは静的に (ロード時に) 決定でき、さまざまなパラメーター値をさまざまなインクルード要素で定義できます。例えば:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

この SQL フラグメントは、次のような他のステートメントで使用できます。

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

6 つの MyBatis キャッシュ

6.1 キャッシングの紹介

MyBatis には、非常に便利に構成およびカスタマイズできる強力なトランザクション クエリ キャッシュ メカニズムが組み込まれています。MyBatis 3 のキャッシュ実装に多くの改良を加え、より強力で構成しやすくしました。
デフォルトでは、1 つのセッションのデータのみをキャッシュするローカル セッション キャッシュのみが有効になっています。グローバルな二次キャッシュを有効にするには、SQL マッピング ファイルに次の行を追加します。

<cache/>

このタグを追加すると、次の効果があります。

  • マップされたステートメント ファイル内のすべての選択ステートメントの結果がキャッシュされます。
  • マップされたステートメント ファイル内のすべての挿入、更新、および削除ステートメントは、キャッシュをフラッシュします。
  • キャッシュは、使用頻度が最も低いアルゴリズム (LRU、Least Recent Used) アルゴリズムを使用して、不要なキャッシュをクリアします。
  • キャッシュは定期的に更新されません (つまり、更新間隔はありません)。
  • キャッシュには、リストまたはオブジェクト (クエリ メソッドによって返されるもの) への 1024 の参照が保持されます。
  • キャッシュは読み取り/書き込みキャッシュとして扱われます。つまり、フェッチされたオブジェクトは共有されず、他の呼び出し元またはスレッドによって行われる可能性のある変更を妨げることなく、呼び出し元が安全に変更できます。

設定時には、一次キャッシュと二次キャッシュにも分けられます。

  • レベル 1 キャッシュ: スレッド レベル キャッシュ、ローカル キャッシュ、sqlSession レベル キャッシュ
  • 第 2 レベルのキャッシュ: 現在のセッションに限定されないグローバル キャッシュ

6.2 レベル 1 キャッシュの使用

最初のレベルのキャッシュは、既定で存在する sqlsession レベルのキャッシュです。次のケースでは、私が 2 つの同一のリクエストを送信したにもかかわらず、SQL ステートメントが 1 回しか実行されていないことがわかりました。これは、最初のクエリで結果がキャッシュされたことを意味します。

@Test
public void test01() {
    
    
     SqlSession sqlSession = sqlSessionFactory.openSession();
     try {
    
    
         EmpDao mapper = sqlSession.getMapper(EmpDao.class);
         List<Emp> list = mapper.selectAllEmp();
         for (Emp emp : list) {
    
    
             System.out.println(emp);
        }
         System.out.println("--------------------------------");
         List<Emp> list2 = mapper.selectAllEmp();
         for (Emp emp : list2) {
    
    
             System.out.println(emp);
        }
    } catch (Exception e) {
    
    
         e.printStackTrace();
    } finally {
    
    
         sqlSession.close();
    }
}

ほとんどの場合、一次キャッシュは問題ありませんが、一次キャッシュが失敗する特殊なケースがいくつかあります。

1. 第 1 レベルのキャッシュは、sqlSession レベルのキャッシュです。アプリケーションで複数の sqlsession のみが有効になっている場合、キャッシュは無効になります。

 @Test
 public void test02(){
    
    
     SqlSession sqlSession = sqlSessionFactory.openSession();
     EmpDao mapper = sqlSession.getMapper(EmpDao.class);
     List<Emp> list = mapper.selectAllEmp();
     for (Emp emp : list) {
    
    
         System.out.println(emp);
    }
     System.out.println("================================");
     SqlSession sqlSession2 = sqlSessionFactory.openSession();
     EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
     List<Emp> list2 = mapper2.selectAllEmp();
     for (Emp emp : list2) {
    
    
         System.out.println(emp);
    }
     sqlSession.close();
     sqlSession2.close();
}

2. クエリの sql ステートメントを記述するときは、渡されたパラメーターに注意する必要があります. パラメーターが矛盾している場合、結果はキャッシュされません. 3. 送信プロセス中にデータが変更された場合、結果はキャッシュされません
.キャッシュされた

@Test
public void test03(){
    
    
    SqlSession sqlSession = sqlSessionFactory.openSession();
    EmpDao mapper = sqlSession.getMapper(EmpDao.class);
    Emp empByEmpno = mapper.findEmpByEmpno(1111);
    System.out.println(empByEmpno);
    System.out.println("================================");
    empByEmpno.setEname("zhangsan");
    int i = mapper.updateEmp(empByEmpno);
    System.out.println(i);
    System.out.println("================================");
    Emp empByEmpno1 = mapper.findEmpByEmpno(1111);
    System.out.println(empByEmpno1);
    sqlSession.close();
}

4. 2 つのクエリ中に、キャッシュを手動でクリアすると、キャッシュも無効になります

@Test
public void test03(){
    
    
    SqlSession sqlSession = sqlSessionFactory.openSession();
    EmpDao mapper = sqlSession.getMapper(EmpDao.class);
    Emp empByEmpno = mapper.findEmpByEmpno(1111);
    System.out.println(empByEmpno);
    System.out.println("================================");
    System.out.println("手动清空缓存");
    sqlSession.clearCache();
    System.out.println("================================");
    Emp empByEmpno1 = mapper.findEmpByEmpno(1111);
    System.out.println(empByEmpno1);
    sqlSession.close();
}

特性

レベル 1 キャッシュの機能:

  1. これはデフォルトで有効になっており、一次キャッシュ localCacheScope=STATEMENT を無効にすることもできます。
  2. スコープ: データベース操作セッションである sqlSession (デフォルト) に基づきます。
  3. キャッシュは、デフォルトでクラス PerpetualCache を実装します。これは、ストレージにマップを使用します。
  4. クエリ後に保存されます
  5. 最初に第 2 レベルのキャッシュから取得し、次に第 1 レベルのキャッシュから
    key==> sqlid+sqlを取得します。

第 1 レベルのキャッシュの無効化:

  1. 異なる sqlSession は、第 1 レベルのキャッシュを無効にします
  2. 同じ SqlSession ですが、異なるクエリ ステートメント
  3. 同一のSqlSession、同一のクエリ文で、期間中に追加・削除・修正操作を行う
  4. 同じSqlSession、クエリ文は同じ、手動でキャッシュクリアを実行

6.3 L2 キャッシュの使用

第 2 レベルのキャッシュはグローバル スコープのキャッシュであり、既定では有効になっておらず、手動で構成する必要があります。
Mybatis provides the interface and implementation of the second-level cache. When the cache is implemented, the entity class is required to implement the Serializable interface. セカンドレベル キャッシュは、sqlSession が閉じられるか送信されるまで有効になりません。

第 2 レベルのキャッシュの使用

手順:
1. 以下の構成をグローバル構成ファイルに追加します。

<setting name="cacheEnabled" value="true"/>

2. タグを使用して、2 番目のレベルのキャッシュを使用してマッピング ファイルをマークする必要があります.
3. エンティティ クラスは、Serializable インターフェイスを実装する必要があります。

@Test
public void test04(){
    
    
    SqlSession sqlSession = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    EmpDao mapper = sqlSession.getMapper(EmpDao.class);
    EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
    Emp empByEmpno = mapper.findEmpByEmpno(1111);
    System.out.println(empByEmpno);
    sqlSession.close();
    Emp empByEmpno1 = mapper2.findEmpByEmpno(1111);
    System.out.println(empByEmpno1);
    sqlSession2.close();
}

キャッシュされたプロパティ

  • eviction: キャッシュのリサイクル戦略を示します。デフォルトは LRU です。
    • LRU: 使用頻度が最も低いオブジェクト。長期間使用されていないオブジェクトを削除します。
    • FIFO: 先入れ先出し、オブジェクトがキャッシュに入る順序で削除されます
    • SOFT: ソフト参照。ガベージ コレクターのステータスとソフト参照ルールに基づいてオブジェクトを削除します。
    • WEAK: 弱い参照。ガベージ コレクターの状態と弱い参照ルールに基づいて、より積極的にオブジェクトを削除します。
  • flushInternal: リフレッシュ間隔 (ミリ秒単位)
    • デフォルトは設定されていません。つまり、リフレッシュ間隔はなく、ステートメントが呼び出されたときにのみキャッシュがリフレッシュされます。
  • サイズ: 参照数、正の整数
    • キャッシュに格納できるオブジェクトの最大数を表します。大きすぎるとメモリ オーバーフローが発生しやすくなります
  • readonly: 読み取り専用、真/偽
    • true: 読み取り専用キャッシュは、これに対するすべての呼び出しに対してキャッシュ オブジェクトの同じインスタンスを返すため、これらのオブジェクトは変更できません。
    • false: 読み取りおよび書き込みキャッシュ。キャッシュ オブジェクトのコピーを返します (シリアル化の実装)。このメソッドはより安全です。既定値です。
//可以看到会去二级缓存中查找数据,而且二级缓存跟一级缓存中不会同时存在数据,因为二级缓存中的数据是在sqlsession 关闭之后才生效的
@Test
public void test05(){
    
    
    SqlSession sqlSession = sqlSessionFactory.openSession();
    EmpDao mapper = sqlSession.getMapper(EmpDao.class);
    Emp empByEmpno = mapper.findEmpByEmpno(1111);
    System.out.println(empByEmpno);
    sqlSession.close();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
    Emp empByEmpno2 = mapper2.findEmpByEmpno(1111);
    System.out.println(empByEmpno2);
    Emp empByEmpno3 = mapper2.findEmpByEmpno(1111);
    System.out.println(empByEmpno3);
    sqlSession2.close();
}

キャッシュ クエリの順序は、最初に第 2 レベルのキャッシュをクエリし、次に第 1 レベルのキャッシュをクエリすることです。

@Test
public void test05(){
    
    
     SqlSession sqlSession = sqlSessionFactory.openSession();
     EmpDao mapper = sqlSession.getMapper(EmpDao.class);
     Emp empByEmpno = mapper.findEmpByEmpno(1111);
     System.out.println(empByEmpno);
     sqlSession.close();
     SqlSession sqlSession2 = sqlSessionFactory.openSession();
     EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
     Emp empByEmpno2 = mapper2.findEmpByEmpno(1111);
     System.out.println(empByEmpno2);
     Emp empByEmpno3 = mapper2.findEmpByEmpno(1111);
     System.out.println(empByEmpno3);
     Emp empByEmpno4 = mapper2.findEmpByEmpno(7369);
     System.out.println(empByEmpno4);
     Emp empByEmpno5 = mapper2.findEmpByEmpno(7369);
     System.out.println(empByEmpno5);
     sqlSession2.close();
}

2次キャッシュの適用範囲
グローバルな2次キャッシュ構成が設定されている場合は、使用する際に注意が必要です. 個々のselectステートメントでは、クエリキャッシュをオフにして特別な設定を完了することができます.

1.設定の設定は、2次キャッシュを有効にするように構成することであり、1次キャッシュはデフォルトで常に有効になっています

<setting name="cacheEnabled" value="true"/>

2. select タグの useCache 属性:
各 select クエリで、現在のクエリが 2 次キャッシュを使用するかどうかを設定できます。これは、2 次キャッシュに対してのみ有効です。

3.
sql タグの flushCache 属性のデフォルト値は、操作の追加、削除、および変更に対して true です。SQL の実行後、第 1 レベルのキャッシュと第 2 レベルのキャッシュがクリアされ、クエリ操作はデフォルトになります。偽に

4. sqlSession.clearCache() は、
第 1 レベルのキャッシュをクリアするためにのみ使用されます。

L2 キャッシュ機能

  1. デフォルトで有効、実装されていません
  2. スコープ: グローバル スコープ、アプリケーション レベルに基づきます。
  3. キャッシュは、デフォルトで PerpetualCache クラスを実装します. これは、ストレージにマップを使用しますが、2 番目のレベルのキャッシュには、さまざまなマッパーの名前空間に従ってマップの追加レイヤーが含まれています.
 : org.apache.ibatis.session.Configuration#caches    key:mapper命名空间   value:erpetualCache.map
 key==> sqlid+sql
  1. トランザクションがコミットされたとき (sqlSession が閉じられたとき)
  2. 最初に第 2 レベルのキャッシュから取得し、次に第 1 レベルのキャッシュから取得します。

達成:

  1. L2 キャッシュを有効にする<setting name="cacheEnabled" value="true"/>
  2. 2 次キャッシュで使用する必要があるマッピング ファイルに追加し、Mapper マッピング ファイルに基づいてキャッシュを実装し、Mapper マッピング ファイルの名前空間に基づいて保存します。
  3. シリアライゼーション インターフェースを実装
    する

失敗:

  1. 同じ名前空間で操作を追加、削除、および変更すると、二次キャッシュが失敗します
    が、失敗したくない場合: SQL の flushCache を false に設定できますが、慎重に設定する必要があります。クエリを保証できない限り、データのダーティな読み取りの問題が発生します データが追加、削除、または変更されることはありません
  2. クエリがデータを二次キャッシュにキャッシュしないようにする useCache="false"
  3. 他のマッパー マッピング ファイルの名前空間で、他の名前空間の追加、削除、変更、および空化を実行する場合は、次のように設定できます。
 <cache-ref namespace="cn.tulingxueyuan.mapper.DeptMapper"/>

6.4 サードパーティのキャッシュを統合する

Redis を統合する

redis-mybatis キャッシュ アダプターの依存関係を追加する

<dependencies>
    <!--添加依赖-->
    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-redis</artifactId>
        <version>1.0.0-beta2</version>
    </dependency>
</dependencies>

リソースのルート ディレクトリに redis.properties を追加します。

host=localhost
port=6379
connectionTimeout=5000
soTimeout=5000
password=无密码可不填
database=0
clientName=

mybatis の二次キャッシュ実装クラスを設定する

<cache
        ...
        type="org.mybatis.caches.redis.RedisCache" ......
/>

ehcacheを統合する

対応する Maven 依存関係をインポートする

<!-- https://mvnrepository.com/artifact/org.ehcache/ehcache -->
 <dependency>
     <groupId>org.ehcache</groupId>
     <artifactId>ehcache</artifactId>
     <version>3.8.1</version>
 </dependency>
 <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
 <dependency>
     <groupId>org.mybatis.caches</groupId>
     <artifactId>mybatis-ehcache</artifactId>
     <version>1.2.0</version>
 </dependency>

ehcache 構成ファイルのインポート

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\ehcache" />

<defaultCache
  maxElementsInMemory="1"
  maxElementsOnDisk="10000000"
  eternal="false"
  overflowToDisk="true"
  timeToIdleSeconds="120"
  timeToLiveSeconds="120"
  diskExpiryThreadIntervalSeconds="120"
  memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>

<!--
属性说明:
l diskStore:指定数据在磁盘中的存储位置。
l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略

以下属性是必须的:
l maxElementsInMemory - 在内存中缓存的element的最大数目
l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上

以下属性是可选的:
l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
-->

マッパー ファイルにカスタム キャッシュを追加する

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

7 つの MyBatis ページング プラグインとリバース エンジニアリング

7.1 ページネーションプラグイン

MyBatis では、プラグイン メカニズムを提供することで、必要に応じて MyBatis の機能を拡張できます。MyBatis の動作原理とプラグインの動作方法を完全に理解していない場合は、プラグインを使用しないことをお勧めします。システムの基本的な動作ロジックが変更され、システムに大きな影響を与えます。
  MyBatis プラグインは、パラメーターの処理、SQL の処理、結果の処理など、元のコードを変更することなくインターセプトすることで、4 つのコア オブジェクトの動作を変更できます。

Mybatis プラグインの典型的な適用シナリオ

ページング機能
mybatis のページングは​​デフォルトでメモリページング (全検索してインターセプト) を基本としており、データ量が多い場合は効率が悪いですが、mybatis プラグインを使用することで動作を変更することができます. StatementHandler クラスの prepare メソッドをインターセプトし、実行する SQL ステートメントをページング ステートメントに変更するだけで済みます。

public フィールドの統一された割り当て
一般的なビジネス システムには、creator、creation time、modifier、modification time の 4 つのフィールドがあります. これら 4 つのフィールドの割り当ては、実際には DAO レイヤーで傍受および処理できます. mybatis プラグインを使用して、 Executor クラスをインターセプトします update メソッドを使用して、関連するパラメーターを一様に割り当てることができます。

パフォーマンスの監視
SQL ステートメントの実行のパフォーマンスを監視するために、ログを使用して、更新、クエリ、および Executor クラスの他のメソッドをインターセプトすることにより、各メソッドの実行時間を記録できます。

その他
実際には、mybatis は依然として非常にスケーラブルです. プラグインのメカニズムに基づいて、実行段階、パラメーター処理段階、文法構築段階、結果セット処理段階など、SQL 実行のさまざまな段階を基本的に制御できます. 具体的には、プロジェクトのビジネスロジックに従って、対応するビジネスを実現できます。

実現思考:
最初の質問:
  オブジェクトのコードを変更せずに、オブジェクトの動作を変更するにはどうすればよいでしょうか? たとえば、元のメソッドの前に何かを行い、元のメソッドの背後で何かを行うなどです。
  回答: MyBatis プラグインの原則であるプロキシ モードを使用することは誰にでも簡単に考えられます。
  
2 番目の質問:
  多数のプラグインを定義できるため、これらすべてのプラグインがリンクを形成します.たとえば、休暇申請書を提出すると、最初にプロジェクト マネージャーによって承認され、次に部門マネージャーによって承認されます。 、次に人事部、次にゼネラルマネージャーによる.マネージャーの承認、レイヤーごとのインターセプトをどのように達成するのですか?
  回答: プラグインはレイヤーごとにインターセプトされるため、別の設計パターン (一連の責任パターン) を使用する必要があります。
  
  前のソース コードでは、mybatis がプラグイン処理にプロキシ モードを使用していることもわかりました.プロキシ モードであるため、MyBatis のどのオブジェクトとメソッドがインターセプトできるかを理解する必要があります.すべての実行中のノードがインターセプトできるわけではありません.変更されました。これらのオブジェクトのメソッドの機能を理解して初めて、独自のプラグインを作成するときにそれらをインターセプトする場所を知ることができます。MyBatis の公式 Web サイトに回答があります。見てみましょう: https://mybatis.org/mybatis-3/zh/configuration.html#plugins
ここに画像の説明を挿入
Executor は CachingExcecutor または BaseExecutor をインターセプトします。Executor を作成するときは、最初に CachingExcecutor が作成され、次にインターセプトがパッケージ化されるためです。それはコード シーケンスからわかります。mybatis のページング プラグインを使用して、インターセプター チェーンのラップからインターセプター チェーンの実行までのプラグイン全体のプロセスを確認できます。
  プラグインの原理を確認する前提で、公式サイトでカスタムプラグインがどのように行われているかを確認する必要があります.公式サイトでは紹介があります.MyBatisが提供する強力なメカニズムを介して、非常に簡単に.プラグインを使用し、Interceptor インターフェイスを実装するだけで、インターセプトするメソッド シグネチャを指定するだけです。ここで私はピットを踏んでSpringbootに統合し、同時にpagehelper-spring-boot-starterを導入しました。これにより、RowBoundsパラメーターの値が一掃されました。つまり、値が設定されていないインターセプトにここで、インターセプトに注意する必要がありますインターセプターに問題がある場合は、Debug を実行して Configuration 構成クラスでインターセプター チェーンのパッケージを確認できます。

カスタム ページネーション プラグイン

@Intercepts({
    
    
        @Signature(type = Executor.class,method = "query" ,args ={
    
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ), // 需要代理的对象和方法
        @Signature(type = Executor.class,method = "query" ,args ={
    
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class} ) // 需要代理的对象和方法
})
public class MyPageInterceptor implements Interceptor {
    
    

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
    
        System.out.println("简易版的分页插件:逻辑分页改成物理分页");

        // 修改sql 拼接Limit 0,10
        Object[] args = invocation.getArgs();
        // MappedStatement 对mapper映射文件里面元素的封装
        MappedStatement ms= (MappedStatement) args[0];
        // BoundSql 对sql和参数的封装
        Object parameterObject=args[1];
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        // RowBounds 封装了逻辑分页的参数 :当前页offset,一页数limit
        RowBounds rowBounds= (RowBounds) args[2];

        // 拿到原来的sql语句
        String sql = boundSql.getSql();
        String limitSql=sql+ " limit "+rowBounds.getOffset()+","+ rowBounds.getLimit();

        //将分页sql重新封装一个BoundSql 进行后续执行
        BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), limitSql, boundSql.getParameterMappings(), parameterObject);

        // 被代理的对象
        Executor executor= (Executor) invocation.getTarget();
        CacheKey cacheKey = executor.createCacheKey(ms, parameterObject, rowBounds, pageBoundSql);
        // 调用修改过后的sql继续执行查询
        return  executor.query(ms,parameterObject,rowBounds, (ResultHandler) args[3],cacheKey,pageBoundSql);
    }
}

インターセプト シグネチャとパラメータの順序には厳密な要件があり、順序に従って対応するメソッドが見つからない場合は、例外がスローされます。

org.apache.ibatis.exceptions.PersistenceException:
            ### Error opening session.  Cause: org.apache.ibatis.plugin.PluginException: 
            Could not find method on interface org.apache.ibatis.executor.Executor named queryv

MyBatis は起動時にタグをスキャンし、Configuration オブジェクトの InterceptorChain に登録します。プロパティのパラメーターは、setProperties() メソッドを呼び出すことによって処理されます。

ページネーション プラグインは、
pom の依存関係を追加するために使用します。

<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper</artifactId>
	<version>1.2.15</version>
</dependency>

プラグインの登録、プラグインを mybatis-config.xml に登録します。

<configuration>

	<plugins>
		<!-- com.github.pagehelper为PageHelper类所在包名 -->
		<plugin interceptor="com.github.pagehelper.PageHelper">
			<property name="helperDialect" value="mysql" />
			<!-- 该参数默认为false -->
			<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
			<!-- 和startPage中的pageNum效果一样 -->
			<property name="offsetAsPageNum" value="true" />
			<!-- 该参数默认为false -->
			<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
			<property name="rowBoundsWithCount" value="true" />
			<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
			<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型) -->
			<property name="pageSizeZero" value="true" />
			<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
			<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
			<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
			<property name="reasonable" value="true" />
			<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
			<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
			<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 -->
			<!-- 不理解该含义的前提下,不要随便复制该配置 -->
			<property name="params" value="pageNum=start;pageSize=limit;" />
		</plugin>
	</plugins>
</configuration>

移行

// 获取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis/mybatis-config.xml");
// 通过加载配置文件获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    
    
    // Mybatis在getMapper就会给我们创建jdk动态代理
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    PageHelper.startPage(1, 5);
    List<Emp> list=mapper.selectAll(); 
    PageInfo<ServiceStation> info = new PageInfo<ServiceStation>(list, 3);                   
          System.out.println("当前页码:"+info.getPageNum());
          System.out.println("每页的记录数:"+info.getPageSize());
          System.out.println("总记录数:"+info.getTotal());
          System.out.println("总页码:"+info.getPages());
          System.out.println("是否第一页:"+info.isIsFirstPage());
          System.out.println("连续显示的页码:");
          int[] nums = info.getNavigatepageNums();
          for (int i = 0; i < nums.length; i++) {
    
    
               System.out.println(nums[i]);
          }     
}  

プロキシと傍受はどのように実装されますか?
  上記のプロキシ可能な 4 つの主要なオブジェクトがプロキシされたのはいつですか? Executor は openSession() のときに作成されます; StatementHandler は SimpleExecutor.doQuery() によって作成されます; これには、パラメーターを処理するための ParameterHandler と結果セットを処理するための ResultSetHandler の作成が含まれます. 作成後、InterceptorChain.pluginAll() が呼び出されてレイヤーごとに返されますレイヤ プロキシ オブジェクトの後に。プロキシは Plugin クラスによって作成されます。書き直した plugin() メソッドでは、returnPlugin.wrap(target, this); を直接呼び出してプロキシ オブジェクトを返すことができます。
  単一のプラグインの場合、プロキシをプロキシできますか? 代理注文と呼び出し注文の関係は?プロキシすることができます。
ここに画像の説明を挿入

プロキシクラスが Plugin なので、最後に Plugin の invoke() メソッドが呼び出されます。最初に、定義されたインターセプターの Intercept() メソッドを呼び出します。インターセプトされたプロキシ オブジェクトのメソッドは、invocation.proceed() を介して呼び出すことができます。
ここに画像の説明を挿入

呼び出しプロセスのシーケンス図:
ここに画像の説明を挿入

PageHelper の原理
  ページング プラグインの簡単な使い方を見てみましょう。

PageHelper.startPage(1, 3);
List<Blog> blogs = blogMapper.selectBlogById2(blog);
PageInfo page = new PageInfo(blogs, 3);

上記のプラグイン メカニズムについては既に紹介しましたが、ここでは当然、関連するコア クラスである PageInterceptor について考えます。Executor の 2 つの query() メソッドはインターセプトされます. ページング プラグインの機能を実現するには、作成した sql を書き換える必要があるため、インターセプト メソッドで操作する必要があります。

 String pageSql = this.dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);

AbstractHelperDialect で getPageSql メソッドを呼び出します。

public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
    
    
        // 获取sql
        String sql = boundSql.getSql();
        //获取分页参数对象
        Page page = this.getLocalPage();
        return this.getPageSql(sql, page, pageKey);
    }

ここで、 this.getLocalPage() が呼び出されることがわかります。このメソッドを見てみましょう。

public <T> Page<T> getLocalPage() {
    
    
  return PageHelper.getLocalPage();
}
//线程独享
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();
public static <T> Page<T> getLocalPage() {
    
    
  return (Page)LOCAL_PAGE.get();
}

PageHelper のローカル スレッド変数の Page オブジェクトがここで呼び出され、設定した PageSize と PageNum がそこから取得されることがわかります。参照してください:

PageHelper.startPage(1, 3);

public static <E> Page<E> startPage(int pageNum, int pageSize) {
    
    
        return startPage(pageNum, pageSize, true);
}

public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
    
    
        Page<E> page = new Page(pageNum, pageSize, count);
        page.setReasonable(reasonable);
        page.setPageSizeZero(pageSizeZero);
        Page<E> oldPage = getLocalPage();
        if (oldPage != null && oldPage.isOrderByOnly()) {
    
    
            page.setOrderBy(oldPage.getOrderBy());
     }
        //设置页数,行数信息
        setLocalPage(page);
        return page;
}

protected static void setLocalPage(Page page) {
    
    
        //设置值
        LOCAL_PAGE.set(page);
}

PageHelper.startPage(1, 3); を呼び出すと、システムは LOCAL_PAGE.set(page) を呼び出して設定し、このローカル変数オブジェクトのパラメーターを SQL 書き換え用のページング プラグインで取得できるようにします。書き換えには多くの実装があります, ここで使用するMysqlの実装:
ここに画像の説明を挿入
 ここでは、ページングプラグインがSQLのコアコードを書き換えていることがわかります. このコードは非常に明確であるため, 詳細に入る必要はありません:

public String getPageSql(String sql, Page page, CacheKey pageKey) {
    
    
        StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
        sqlBuilder.append(sql);
        if (page.getStartRow() == 0) {
    
    
            sqlBuilder.append(" LIMIT ");
            sqlBuilder.append(page.getPageSize());
        } else {
    
    
            sqlBuilder.append(" LIMIT ");
            sqlBuilder.append(page.getStartRow());
            sqlBuilder.append(",");
            sqlBuilder.append(page.getPageSize());
            pageKey.update(page.getStartRow());
        }

        pageKey.update(page.getPageSize());
        return sqlBuilder.toString();
}

これは、PageHelper がページング効果を実現するために SQL を段階的に書き換える方法です。
主なクラスの概要:
  ここに画像の説明を挿入

7.2 MyBatis リバースエンジニアリング

pom の依存関係を導入する

<dependency>
   <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.4.0</version>
</dependency>

構成ファイルの書き込み:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
<!-- 指定数据库驱动
	用java代码的方式生成可以不指定(只需要吧mybatis-generator和数据库驱动依赖到项目)

  <classPathEntry location ="F:\java\jar\mysql-connector-java-5.1.22-bin.jar" /> -->
  
  <!-- targetRuntime
  	MyBatis3  可以生成通用查询,可以指定动态where条件
    MyBatis3Simple 只生成CURD
   -->
  <context id="DB2Tables" targetRuntime="MyBatis3">
  
  <!-- 关于注释的生成规则 -->
	<commentGenerator>
	<!-- 设置不生成注释 -->
		<property name="suppressAllComments" value="true"/>
	</commentGenerator>
	
  <!-- 数据库的连接信息 -->
    <jdbcConnection driverClass="com.mysql.jdbc.Driver"
        connectionURL="jdbc:mysql://localhost:3306/bookstore"
        userId="root"
        password="root">
    </jdbcConnection>

<!-- java类型生成方式 -->
    <javaTypeResolver >
    <!-- forceBigDecimals 
	    	true 当数据库类型为decimal 或number 生成对应的java的BigDecimal
	    	false 会根据可数据的数值长度生成不同的对应java类型
	    useJSR310Types
	        false 所有数据库的日期类型都会生成java的 Date类型	
	        true  会将数据库的日期类型生成对应的JSR310的日期类型
	        		比如 mysql的数据库类型是date===>LocalDate
	        		必须jdk是1.8以上
    -->
      <property name="forceBigDecimals" value="false" />
    </javaTypeResolver>

	<!-- pojo的生成规则 -->
    <javaModelGenerator  
    	targetPackage="cn.tuling.pojo" targetProject="./src/main/java">
      <property name="enableSubPackages" value="true" />
      <property name="trimStrings" value="true" />
    </javaModelGenerator>

	<!-- sql映射文件的生成规则 -->
    <sqlMapGenerator targetPackage="cn.tuling.mapper"  targetProject="./src/main/resources">
      <property name="enableSubPackages" value="true" />
    </sqlMapGenerator>

	<!-- dao的接口生成规则 UserMapper-->
    <javaClientGenerator type="XMLMAPPER" targetPackage="cn.tuling.mapper"  targetProject="./src/main/java">
      <property name="enableSubPackages" value="true" />
    </javaClientGenerator>

    <table tableName="emp" domainObjectName="Emp" mapperName="EmpMapper" ></table>
 	<table tableName="dept" domainObjectName="Dept" mapperName="DeptMapper" ></table>


  </context>
</generatorConfiguration>

テストクラスを書く

public class MBGTest {
    
    

    @Test
    public void test01() throws Exception {
    
    
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        File configFile = new File("generatorConfig.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }
}

移行

@Test
public void test01() {
    
    
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    
    
        // Mybatis在getMapper就会给我们创建jdk动态代理
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);

        Emp emp = mapper.selectByPrimaryKey(4);
        System.out.println(emp);
    }
}



/**
 * Mybatis3生成调用方式
 */
@Test
public void test02() {
    
    
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    
    
        // Mybatis在getMapper就会给我们创建jdk动态代理
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);

        // 使用Example实现动态条件语句
        EmpExample empExample=new EmpExample();
        EmpExample.Criteria criteria = empExample.createCriteria();
        criteria.andUserNameLike("%帅%")
                .andIdEqualTo(4);

        List<Emp> emps = mapper.selectByExample(empExample);
        System.out.println(emps);
    }
}

おすすめ

転載: blog.csdn.net/qq_33417321/article/details/121435846