MyBatis外掛 - 通用mapper

紫邪情發表於2022-04-25

1、簡單認識通用mapper

1.1、瞭解mapper

  • 作用:就是為了幫助我們自動的生成sql語句 [ ps:MyBatis需要編寫xxxMapper.xml,而逆向工程是根據entity實體類來進行生成的,有時由於業務需要,會讓實體類與資料庫欄位名不對應,所以逆向工程生成的xxxMapper.xml配置就會有問題。其實:通用Mapper和JPA很像 ]
  • 通用mapper是MyBatis的一個外掛,是pageHelper的同一個作者進行開發的
  • 作者gitee地址:https://gitee.com/free
  • 通用mapper官網地址:https://gitee.com/free/Mapper
  • 通用mapper文件介紹地址:https://gitee.com/free/Mapper/wikis/Home


1.2、學習通用mapper需要的知識

  • Mybatis
  • Spring



2、玩通用mapper

2.1、準備工作

建測試表

CREATE TABLE `user` (
  `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '使用者id',
  `user_name` varchar(20) DEFAULT NULL COMMENT '使用者名稱',
  `user_sex` varchar(2) DEFAULT NULL COMMENT '使用者性別',
  `user_salary` decimal(5,2) DEFAULT NULL COMMENT '使用者薪資',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='使用者表';


insert  into `user`(`user_id`,`user_name`,`user_sex`,`user_salary`) values 
(1,'紫邪情','女',100.00),
(2,'紫玲','女',50.00),
(3,'張三','男',999.99);



建立Spring專案 並 匯入依賴

    <!--  spring整合mybatis的依賴  -->
    <!--  1、spring需要的依賴  -->
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>
		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>

        <!-- 2、mybatis的依賴 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>

        <!-- spring整合mybatis的第三方依賴 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.2</version>
        </dependency>

        <!--    1、資料庫驅動    -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.6</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <!-- 通用mapper的依賴 -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>3.4.2</version>
        </dependency>

    </dependencies>


編寫SSM框架整合的xml檔案


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
				https://www.springframework.org/schema/beans/spring-beans.xsd
				http://www.springframework.org/schema/aop
				https://www.springframework.org/schema/aop/spring-aop.xsd
				http://www.springframework.org/schema/context
				https://www.springframework.org/schema/context/spring-context.xsd
				http://www.springframework.org/schema/tx
				https://www.springframework.org/schema/tx/spring-tx.xsd
			">

    <!--  1、獲取資料來源 —— 使用druid -->
    <context:property-placeholder location="classpath:db.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${druid.driverClassName}"/>
        <property name="url" value="${druid.url}"/>
        <property name="username" value="${druid.username}"/>
        <property name="password" value="${druid.password}"/>
    </bean>

    <!--  2、獲取SQLSessionFactory工廠-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!-- 把mybatis整合進來 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/> <!-- 整合mybatis-config.xml -->
    </bean>

    <!--  3、配置事務管理  -->
    <!--  宣告事務託管  -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--  說明哪些方法要進行事務託管 —— 即:通知類 -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="*" rollback-for="Exception"/>
        </tx:attributes>
    </tx:advice>
    <!--  編寫切面  -->
    <aop:config>
        <!-- 切點 -->
        <aop:pointcut id="pointCut" expression="execution( * cn.xiegongzi.mapper.*.*(..) )"/>
        <!-- 組裝切面 ——— 切點和通知類組裝 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"/>
    </aop:config>

    <!-- 4、掃描mapper層,整合通用mapper的唯一一個注意點
        原始SSM整合寫法是:org.mybatis.spring.mapper.MapperScannerConfigurer
        現在用通用mapper替換:tk.mybatis.spring.mapper.MapperScannerConfigurer
        為什麼通用mapper可以替換掉mybatis?
            因為:通用mapper的MapperScannerConfigurer在底層繼承了mybatis的MapperScannerConfigurer,可以點原始碼
    -->
    <bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.zixieqing.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
	
	<!--掃描service層-->
    <context:component-scan base-package="cn.zixieqing.service"/>


</beans>



  • 注意點:在掃描mapper層時,使用通用mapper覆蓋mybatis,寫法不太一樣

image


  • 我的專案結構如下

image



建對應的實體類

  • 注意點:資料型別用包裝類,因為包裝類可以判斷null值,這個涉及到通用mapper的原理,資料型別用包裝類在MaBatis中就已經知道的事情
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class UserEntity implements Serializable {

    private Integer userId;
    private String userName;
    private String userSex;
    private Double userSalary;
}




2.2、玩通用mapper的基礎API

先看一下通用mapper大概有哪些API

// 這裡介面直接繼承通用mapper介面即可
//    注意:泛型中的資訊就是實體類
public interface UserMapper extends Mapper<UserEntity> {		// 看原始碼,點mapper即可進入

}


image


看看BaseMapper

image

  • 其他的都是差不多的套路,歸納起來其實就是增刪查改的封裝,然後做了不同的細分,需要繼續檢視的,那就往後挨個去點選



2.2.1、和select相關

2.2.1、selectOne方法 和 @Table註解

image



編寫測試類 並啟動

package cn.zixieqing;


import cn.zixieqing.entity.*;
import cn.zixieqing.mapper.*;
import org.junit.*;
import org.springframework.context.*;
import org.springframework.context.support.*;


public class MyTest {

    @Test
    public void selectOneTest() {

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

        UserEntity userEntity = new UserEntity();
        userEntity.setUserId(1)
                .setUserName("紫邪情")
                .setUserSex("女")
                .setUserSalary(new Double(100));
        System.out.println(userMapper.selectOne(userEntity));
    }
}



  • 出現報錯:Table 'mapper_study.user_entity' doesn't exist

image


  • 原因就是編寫的實體類名叫做UserEntity,而資料庫的表名叫做user,解決方式就是在實體類中加入@Table註釋,注意此註解是import javax.persistence.*;包下的

image


另外的selectxxx方法直接一點就出來了,基本上都是見名知意,就算不知道的原始碼中也有解釋,通用mapper就是國內人寫的


  • 至於@Column註解就是見名知意,用來處理實體類的欄位和資料庫中的欄位不一致的問題
    • 預設規則:
      • 實體類欄位:駝峰式命名
      • 資料庫表欄位:使用“_”區分各個單詞

image



2.2.2、觀察日誌總結selectOne方法

image

  • selectOne()是將封裝的實體類作為了WHERE子句的條件
    • 這裡是使用了非空的值作為的WHERE子句

image

  • 在條件表示式中使用“=”進行比較
  • 注意點:要求必須返回一個實體類結果,如果有多個,則會丟擲異常



2.2.3、xxxByPrimaryKey方法 和 @Id註解

測試

    @Test
    public void selectByPrimaryKey() {
        System.out.println(userMapper.selectByPrimaryKey(3));
    }


image

  • 結果:發現將實體類的所有欄位屬性都作為WHERE子句的條件了
  • 解決辦法:給實體類中對應的資料庫表的主鍵欄位加上@Id註解

image


image



2.2.4、xxxByPrimaryKey方法 和 @Id註解總結

@Id註解

  • 為了將實體類欄位屬性和對應的資料庫表主鍵做匹配

  • 原因:通用mapper在執行xxxByPrimaryKey方法時會出現兩種情況:

    • 1、沒有加@Id註解時,通用mapper會將實體類的所有屬性作為聯合主鍵來匹配資料庫表的主鍵,故而會出現將實體類中的欄位屬性全部作為WHERE子句後面的條件欄位

      • SELECT user_id,user_name,user_sex,user_salary 
        FROM user 
        WHERE user_id = ? AND user_name = ? AND user_sex = ? AND user_salary = ?
        
        
    • 2、使用@Id主鍵將實體類中的欄位屬性和資料庫表中的主鍵做明確匹配

image

image


xxxByPrimaryKey方法

  • 需要使用@Id註解來讓實體類中的欄位屬性和資料庫表中的主鍵做明確匹配,否則:通用mapper預設將實體類的所有欄位屬性作為聯合主鍵來進行匹配



2.2.5、select方法
  • 傳什麼,就用什麼來拼接WHERE子句的條件

測試

    @Test
    public void select() {
        UserEntity userEntity = new UserEntity();
        userEntity.setUserName("紫邪情");
        System.out.println(userMapper.select(userEntity));
    }

image



2.2.6、xxxSelective方法
  • 可選擇的嘛
  • 非主鍵欄位,如果不為null,則就加入sql語句
  • 注意:是非null啊,所以前面才說實體類的型別最好用包裝類


2.2.2、和insert相關

2.2.2.1、insert方法

測試

    @Test
    public void insertTest() {

        UserEntity userEntity = new UserEntity();
        userEntity.setUserId(4)
                .setUserName("不知火舞");

        // 這個API會將null也拼接到SQL語句中
        System.out.println(userMapper.insert(userEntity));
    }


image



2.2.2.2、insertSelective方法
    @Test
    public void insertSelective() {
        UserEntity userEntity = new UserEntity();
        userEntity.setUserName("百里守約")
                .setSex("老六");

        // 這個API會將非null的欄位拼接說起來語句中
        userMapper.insertSelective(userEntity);
    }

image



2.2.2.3、@GeneratedValue註解

image



2.2.3、和update相關

2.2.3.1、updateByPrimaryKeySelective方法
  • 這個其實看一眼就知道了,也就是:根據主鍵把不為null值的欄位修改掉,即:set後面的欄位就是實體類中不為null的欄位
    @Test
    public void updateByPrimaryKeySelectiveTest() {

        System.out.println( userMapper.updateByPrimaryKeySelective( new UserEntity().setUserId(1).setUserName("小紫") ) );
    }

image



2.2.4、和delete相關

2.2.4.1、delete方法
  • 切記:使用時,記得把實體類值傳進去,否則:若是null的實體類,則:SQL語句就沒有WHERE條件了,繼而:變成全表的邏輯刪除了
  • 原理:還是一樣的,使用非null的欄位作為WHERE子句條件

image



2.2.4.2、deleteByPrimaryKey方法
  • 見名知意,直接通過主鍵來刪
    @Test
    public void deleteByPrimaryKeyTest() {
        userMapper.deleteByPrimaryKey(2);
    }

image



2.2.5、@Transient註解

  • 一般情況下,實體中的欄位和資料庫表中的欄位是一一對應的,但是也有很多情況我們會在實體中增加一些額外的屬性,這種情況下,就需要使用 @Transient 註解來告訴通用 Mapper 這不是表中的欄位
@Transient
private String otherThings; //非資料庫表中欄位



2.3、QBC查詢

  • QBC全稱:query by criteria 也就是通過規則( criteria )來查詢

2.3.1、Criteria物件

public class ExampleTest {

    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    // 1、建立Example物件
    Example example = new Example(UserEntity.class);


    @Test
    public void exampleTest() {

        // 2、使用Example建立Criteria物件
        Example.Criteria criteria = example.createCriteria();

        // 3、新增規則 下面這些都是Criteria能夠調的API,還有其他的,需要時再說
        /*
            andGreaterThan 即:>               andGreaterThanOrEqualTo 即:>=
            andLessThan 即:<                  andLessThanOrEqualTo 即:<=
            andIn 即:就是SQL的in               andNotIn  就是SQL的not in
            andBetween  即:SQL的between       andNotBetween 即SQL中的not between
            andLike 即sql中的like              andNotLike  即SQL的not like
            要看這些比較符,直接點andGreaterThan看原始碼,裡面都有各種介紹 
        */
        criteria.andGreaterThan("userSalary", 50).andLessThan("userSalary", 200);

        // 4、呼叫Example封裝的API 不止selectByExample這一個,還有其他的CRUD
        List<UserEntity> userEntities = userMapper.selectByExample(example);

        for (UserEntity userEntity : userEntities) {
            System.out.println(userEntity);
        }
    }
}

image



2.3.2、Example物件能呼叫的API

2.3.2.1、CreateCriteria()
    @Test
    public void createCriteriaTest() {

        // createCriteria建立規則 - 有一個別扭的注意點
        Example.Criteria criteria01 = example.createCriteria();
        Example.Criteria criteria02 = example.createCriteria();
        // 使用Example呼叫or()時,傳入的這個Criteria物件的引數有點彆扭
        // 新增規則1
        criteria01.andGreaterThan("userId", 1).andLessThan("userId", 6);
        // 新增規則2
        criteria02.andGreaterThan("userSalary", 100).andLessThan("userSalary", 500);
        /*
        * 拼接的SQL語句:
        *   SELECT user_id,user_name,user_sex,user_salary
        *   FROM user
        *   WHERE ( user_id > ? and user_id < ? ) or ( user_salary > ? and user_salary < ? )
        * */
        // 彆扭之處就在這裡:是將規則2 criteria02 使用or拼接起來,理論上應該是criteria01.or(criteria02)
        // 但是:卻是使用example來調的or( Criteria criteria ),所以感覺example就相當於是criteria01一樣,有點彆扭
        example.or(criteria02);
        List<UserEntity> userEntities = userMapper.selectByExample(example);
        userEntities.forEach(System.out::println);
    }


image



2.3.2.2、orderBy( String property )排序
    @Test
    public void orderByTest() {

        // userSalary 排序欄位      desc 排序方式 - 降序desc 升序asc
        example.orderBy("userSalary").desc();

        userMapper.selectByExample(example).forEach(System.out::println);
    }

image



2.3.2.3、setDistinct( boolean isDistinct )去重
    @Test
    public void setDistinctTest() {
        example.setDistinct(true);
        userMapper.selectByExample(example).forEach(System.out::println);
    }



2.3.2.4、selectProperties( String... properties )設定select後的欄位
    // 設定拼接SQL的select後面的欄位
    @Test
    public void selectPropertiesTest() {
        // 拼接的SQL語句: SELECT user_id , user_name FROM user
        // 預設是* 即:實體類的所有欄位都拼接上去了
        example.selectProperties("userId","userName");

        userMapper.selectByExample(example).forEach(System.out::println);
    }



2.3.2.5、excludeProperties(String... properties)設定select後不包含的欄位
    // 設定select後不包含的欄位
    @Test
    public void excludePropertiesTest() {

        // SQL語句 SELECT user_name , user_sex FROM user 
        example.excludeProperties("userId", "userSalary");

        userMapper.selectByExample(example).forEach(System.out::println);
    }


  • 其他的API直接用example點就出來了,都是見名知意的


2.4、通用mapper逆向工程

2.4.1、pom.xml配置

    <!-- 注意:別少了這個依賴啊,下面plugins中的依賴,那只是外掛需要的依賴
            沒有引入這個dependency的通用mapper依賴的話,那麼生成的程式碼需要引入一些包,到時就是不存在,會報錯的
    -->
    <dependencies>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>4.0.0-beta3</version>
        </dependency>

        <!-- 有些人可能會出現生成的mapper介面層報錯,說的是:rowBounds不存在 檢視import發現原始碼是引入的org.mybatis
                但是目前我用得時候並沒有報錯,所以:為了以防萬一還是加上這個org.mybatis依賴
        -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.6</version>
                <configuration>
                    <!-- generatorConfig.xml逆向工程配置檔案所在地 根據需要自行修改 -->
                    <configurationFile>
                        ${basedir}/generatorConfig.xml
                    </configurationFile>
                    <overwrite>true</overwrite>
                    <verbose>true</verbose>
                </configuration>
                <!-- 通用mapper逆向工程需要的兩個依賴 -->
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.47</version>
                    </dependency>
                    <dependency>
                        <groupId>tk.mybatis</groupId>
                        <artifactId>mapper</artifactId>
                        <version>4.0.0-beta3</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>


  • 若是出現上述報RowBounds不存在的原因在下面這裡

image



2.4.2、generatorConfig.xml配置

<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!-- 引入外部資料庫配置檔案 最好使用引入,否則:下面資料庫配置那裡奇葩要求很多 -->
    <properties resource="db.properties"/>

    <!-- MySQL基礎資訊 就是起始和結束分隔符  如:`` -->
    <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <!-- 通用mapper外掛 -->
        <!--
            type 是通用mapper外掛,這個可以配置在前面引入的那個外部配置檔案中,即配置成如下:
                mapper.plugin=tk.mybatis.mapper.generator.MapperPlugin
                然後在這裡使用${mapper.plugin}引入,這種方式方便管理
        -->
        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <!-- 這是mapper介面層中extend繼承的那個類,即:public interface userMapper extends Mapper<User> -->
            <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
            <!-- 這是區別大小寫 如:user 和 User -->
            <property name="caseSensitive" value="true"/>
        </plugin>

        <!-- 資料庫 -->
        <jdbcConnection driverClass="${jdbc.driver}"
                        connectionURL="${jdbc.url}"
                        userId="${jdbc.username}"
                        password="${jdbc.password}">
        </jdbcConnection>

        <!-- 實體類 -->
        <javaModelGenerator targetPackage="cn.zixieqing.entity"
                            targetProject="src/main/java"/>

        <!-- xxxMapper.xml所在位置 -->
        <sqlMapGenerator targetPackage="mapper"
                         targetProject="src/main/resources"/>

        <!-- mapper介面層 -->
        <javaClientGenerator targetPackage="cn.zixieqing.mapper"
                             targetProject="src/main/java"
                             type="XMLMAPPER"/>

        <!-- 資料庫表名 和 實體類生成關係
            如果感覺每個表都配置麻煩,那麼直接改變tableName的值即可,即:tableName="%"
            但是:此種方式的預設規則是採用 _ 轉駝峰命名,如:table_name  ——> TableName
            可是:有時我們並不需要這樣命名,此時就需要使用tableName 和 domainObjectName兩個配置項一起來配置
                tableName 資料庫表名
                domainObjectName  生成的實體類名
        -->
        <table tableName="user" domainObjectName = "User">
            <!-- 主鍵生成策略 -->
            <generatedKey column="user_id" sqlStatement="JDBC"/>
        </table>
    </context>
</generatorConfiguration>


引入的外部檔案db.properties的配置

# 資料庫配置
jdbc.driver=com.mysql.jdbc.Driver

# 注意:建議使用db.properties配置從而在generatorConfig.xml中引入的原因就在這裡
# 在這裡可以在這個url後面拼接引數,如:useSSL=false
# 若是直接把這些配置寫到generatorConfig.xml中,那麼後面的引數配置就有幾個奇葩的地方
# 			1、引數之間不是通過&隔開,而是需要使用;分號隔開  如:useSSL=false;useUnicode=true
#			2、false / true等值需要使用``括起來,具體可以嘗試,然後看報的ERROR
jdbc.url=jdbc:mysql://localhost:3306/mapper_study?useSSL=false&useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=root

#c3p0
jdbc.maxPoolSize=50
jdbc.minPoolSize=10
jdbc.maxStatements=100
jdbc.testConnection=true

# 通用Mapper配置 若是在generatorConfig.xml的plugin配置中是通過引入的方式來做的,那麼就可以在這裡配置這兩個資訊
# 從而方便管理
# mapper.plugin=tk.mybatis.mapper.generator.MapperPlugin
# mapper.Mapper=tk.mybatis.mapper.common.Mapper



2.4.3、啟動

  • 在 pom.xml 這一級目錄的命令列視窗執行 mvn mybatis-generator:generate即可

image

image



2.5、自定義mapper

image

  • 自定義mapper介面的作用:根據實際需要自行重組mapper介面【 ps:即 並不是通用mapper中的所有介面和方法都需要 】


2.5.1、玩一下自定義mapper介面

1、自定義自己要的mapper介面

package cn.zixieqing.common;


import tk.mybatis.mapper.common.*;


public interface CustomMapper<T> extends BaseMapper<T> {
    // 這個自定義的mapper,想繼承前面畫的通用mapper中的哪個介面都可以
}



2、編寫業務mapper

package cn.zixieqing.mapper;

import cn.zixieqing.common.*;
import cn.zixieqing.entity.*;


public interface UserMapper extends CustomMapper<UserEntity> {
}


image


  • 注意點:別把自定義mapper和業務mapper放到一個包中,會報錯


3、修改applicationContext.xml檔案的MapperScannerConfigurer配置

    <!-- 4、掃描mapper層,整合通用mapper的唯一一個注意點
        原始SSM整合寫法是:org.mybatis.spring.mapper.MapperScannerConfigurer
        現在用通用mapper替換:tk.mybatis.spring.mapper.MapperScannerConfigurer
        為什麼通用mapper可以替換掉mybatis?
            因為:通用mapper的MapperScannerConfigurer在底層繼承了mybatis的MapperScannerConfigurer,可以點原始碼
    -->
    <bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.zixieqing.mapper"/>
        <!--<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>-->
        <property name="properties">
            <value>
                <!--自定義介面所在的包路徑-->
                mapper=cn.zixieqing.common.CustomMapper
            </value>
        </property>
    </bean>


image



4、測試

    @Test
    public void customMapperTest() {

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper",UserMapper.class);

        userMapper.selectAll().forEach(System.out::println);

    }


image



補充1、要是不想寫applicationContext.xml中MapperScannerConfigurer的那個配置

image

  • 那把內容註釋掉,在自定義mapper介面的地方加個註解@RegisterMapper就搞定了

image



補充2、如果將自定義mapper介面 和 業務mapper介面放到一個包中了

image


  • 一執行就會報錯
tk.mybatis.mapper.MapperException: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to java.lang.Class

image


  • 原因就是利用反射,獲取類物件時失敗,即:原因如下

image

image



2.6、瞭解通用mapper的二級快取

  • 一二級快取的概念哪些就跳過了,MyBatis中已經見過了,這裡玩通用mapper的配置
  • 測試:自行編寫一個業務mapper,然後去繼承mapper<T>,從而多次執行,會發現SQL執行了多次

image


  • 注意:需要讓mapper<T>中的實體類T實現Serializable介面,從而讓其擁用序列號,否則:會報錯的

image


修改mybatis-config.xml檔案

    <settings>
        <!--顯示開啟快取-->
        <setting name="cacheEnabled" value="true"/>
    </settings>


在業務mapper介面中新增@CacheNamespace註解

@CacheNamespace
public interface UserMapper extends Mapper<UserEntity> {
}


再次測試

image



2.7、型別處理器 typeHandler

  • 這裡說的型別是簡單型別和複雜型別,注意:和Java中說的基本型別和引用型別不是一回事,不是說基本型別就一定是簡單型別,這裡不用去考慮基本和引用的問題
  • 簡單型別和複雜型別可以參考一對一和一對多這兩種
  • 簡單型別:只有一個值
  • 複雜型別:有多個值

image

image


  • 而上面這種,對於userName來說,是無法進行CRUD的

image


  • 這種情況就是複雜型別,而通用mapper預設是沒處理的,就有點類似於在上述例子的userName上加了一個@Transient註解,從而忽略了該欄位,從而造成的效果就是:去資料庫中找對應的欄位值時沒找到,從資料庫中找到資料,然後返還給物件時沒有相應的物件可以接受

image


解決辦法:自定義型別處理器

image


  • 具體操作流程如下:

    • 1、建立一個型別處理器的類,然後實現TypeHandler<T>介面,其中:T就是要處理的那個型別,如:上述例子的NameEntity

    • 2、實現裡面的四個方法

      •     @Override
            public void setParameter(PreparedStatement preparedStatement, int i, NameEntity nameEntity, JdbcType jdbcType) throws SQLException {
        
            }
        
            @Override
            public NameEntity getResult(ResultSet resultSet, String s) throws SQLException {
                return null;
            }
        
            @Override
            public NameEntity getResult(ResultSet resultSet, int i) throws SQLException {
                return null;
            }
        
            @Override
            public NameEntity getResult(CallableStatement callableStatement, int i) throws SQLException {
                return null;
            }
        
        
    • 例項邏輯編寫如下

      • public class NameHandler implements TypeHandler<NameEntity> {
        
            /**
             * 這個方法就是:物件NameEntity ——> 資料庫的流程規則,可以將其理解為序列化流程 但是完全不一樣啊
             * 只是說:像序列化一樣把資料轉成一個樣
             * @param ps
             * @param i
             * @param nameEntity
             * @param jdbcType
             * @throws SQLException
             */
            @Override
            public void setParameter(PreparedStatement ps, int i, NameEntity nameEntity, JdbcType jdbcType) throws SQLException {
        
                // 1、驗證NameEntity
                if ( null == nameEntity) {
                    return;
                }
        
                // 2、取出nameEntity中的值
                String firstName = nameEntity.getFirstName();
                String lastName = nameEntity.getLastName();
        
                // 3、把取出的值 拼接成 一個字串
                // 自定義規則:使用 - 進行隔開
                StringBuilder builder = new StringBuilder();
                builder.append(firstName)
                        .append("-")
                        .append(lastName);
        
                // 4、拼接SQL的引數
                ps.setString(i,builder.toString() );
            }
        
            /**
             * 這下面三個是過載,是為了解決:資料庫 ——> 物件NameEntity的流程,類似於反序列化,把另一個東西轉成正常需要的樣子
             * @param resultSet
             * @param columnName
             * @return
             * @throws SQLException
             */
            @Override
            public NameEntity getResult(ResultSet resultSet, String columnName ) throws SQLException {
                // 1、從結果集ResultSet根據欄位名取出欄位值
                String columnValue = resultSet.getString(columnName);
        
                // 2、驗證columnValue
                if ( null == columnValue || columnValue.length() == 0 || !columnValue.contains("-") ) {
                    return null;
                }
        
                // 3、根據“-”對columnValue進行拆分
                String[] column = columnValue.split("-");
        
                // 4、把拆分之後的值 給到 物件的對應值
                return new NameEntity().setFirstName( column[0] ).setLastName( column[1] );
            }
        
            @Override
            public NameEntity getResult(ResultSet resultSet, int i) throws SQLException {
        
                // 1、從結果集ResultSet根據欄位名取出欄位值
                String columnValue = resultSet.getString(i);
        
                // 2、驗證columnValue
                if ( null == columnValue || columnValue.length() == 0 || !columnValue.contains("-") ) {
                    return null;
                }
        
                // 3、根據“-”對columnValue進行拆分
                String[] column = columnValue.split("-");
        
                // 4、把拆分之後的值 給到 物件的對應值
                return new NameEntity().setFirstName( column[0] ).setLastName( column[1] );
            }
        
            @Override
            public NameEntity getResult(CallableStatement cs, int i) throws SQLException {
        
        
                // 1、從CallableStatement 根據 索引取出欄位值
                String columnValue = cs.getString(i);
        
                // 2、驗證columnValue
                if ( null == columnValue || columnValue.length() == 0 || !columnValue.contains("-") ) {
                    return null;
                }
        
                // 3、根據“-”對columnValue進行拆分
                String[] column = columnValue.split("-");
        
                // 4、把拆分之後的值 給到 物件的對應值
                return new NameEntity().setFirstName( column[0] ).setLastName( column[1] );
            }
        }
        
        
        
      • 3、註冊型別處理器

        • 第一種( 欄位級別 ):使用@ColumnType(typeHandler = xxxx.class)註解

        • image

        • image

          • 注意啊:我這裡是改資料庫了的,這是做的查詢嘛,要是資料庫中的資料沒符合規範,那還是查不到
          • image
        • 第二種( 全域性配置 ):在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>
          
              <settings>
                  <setting name="logImpl" value="LOG4J"/>
                  <!--顯示開啟快取-->
                  <setting name="cacheEnabled" value="true"/>
              </settings>
          
              <typeAliases>
                  <package name="cn.xiegongzi.entity"/>
              </typeAliases>
          
              <typeHandlers>
                  <!-- 
                      handler 處理器位置
                      javaType 要處理的是哪個物件
                  -->
                  <typeHandler handler="cn.zixieqing.handler.NameHandler" 
                               javaType="cn.zixieqing.entity.NameEntity"/>
              </typeHandlers>
          
          </configuration>
          
          
        • 給用到該型別的地方新增@Column註解

          • @Data
            @AllArgsConstructor
            @NoArgsConstructor
            @Accessors(chain = true)
            @Table(name = "user")
            @ToString
            public class UserEntity implements Serializable {
            
                private static final long serialVersionUID = -5580827379143778431L;
            
                private Integer userId;
            
                /**
                 * @Transient
                 *
                 * @ColumnType(typeHandler = NameHandler.class)
                 */
                @Column
                private NameEntity userName;
            
                private String userSex;
            
                private Double userSalary;
            }
            
            


3、最後,附上markdown地址

相關文章