1. 程式人生 > >spring boot 學習筆記 (7)MyBatis XML 配置版

spring boot 學習筆記 (7)MyBatis XML 配置版

MyBatis 是現如今最流行的 ORM 框架之一,我們先來了解一下什麼是 ORM 框架。

ORM 框架

物件關係對映(Object Relational Mapping,ORM)模式是一種為了解決面向物件與關係資料庫存在的互不匹配的現象的技術。簡單的說,ORM 是通過使用描述物件和資料庫之間對映的元資料,將程式中的物件自動持久化到關係資料庫中。

為什麼需要 ORM?

當你開發一個應用程式的時候(不使用 O/R Mapping),可能會寫不少資料訪問層程式碼,用來從資料庫儲存、刪除、讀取物件資訊等;在 DAL 中寫了很多的方法來讀取物件資料、改變狀態物件等任務,而這些程式碼寫起來總是重複的。針對這些問題 ORM 提供瞭解決方案,簡化了將程式中的物件持久化到關係資料庫中的操作。

ORM 框架的本質是簡化程式設計中操作資料庫的編碼,在 Java 領域發展到現在基本上就剩兩家最為流行,一個是宣稱可以不用寫一句 SQL 的 Hibernate,一個是以動態 SQL 見長的 MyBatis,兩者各有特點。在企業級系統開發中可以根據需求靈活使用,會發現一個有趣的現象:傳統企業大都喜歡使用 Hibernate,而網際網路行業通常使用 MyBatis。

MyBatis 介紹

MyBatis 是一款標準的 ORM 框架,被廣泛的應用於各企業開發中。MyBatis 最早是 Apache 的一個開源專案 iBatis,2010 年這個專案由 Apache Software Foundation 遷移到了 Google Code,並且改名為 MyBatis,2013 年 11 月又遷移到 Github。從 MyBatis 的遷移史,也可以看出原始碼託管平臺的發展史,GitHub 目前已經成為世界上最大的開源軟體託管平臺,建議大家多多關注這個全球最大的同性社交網站。

MyBatis 支援普通的 SQL 查詢,儲存過程和高階對映的優秀持久層框架。MyBatis 消除了幾乎所有的 JDBC 程式碼和引數的手工設定以及對結果集的檢索封裝。MaBatis 可以使用簡單的 XML 或註解用於配置和原始對映,將介面和 Java 的 POJO(Plain Old Java Objects,普通的 Java 物件)對映成資料庫中的記錄。

作為一款使用廣泛的開源軟體,它的特點有哪些呢?

優點

  • SQL 被統一提取出來,便於統一管理和優化
  • SQL 和程式碼解耦,將業務邏輯和資料訪問邏輯分離,使系統的設計更清晰、更易維護、更易單元測試
  • 提供對映標籤,支援物件與資料庫的 ORM 欄位關係對映
  • 提供物件關係對映標籤,支援物件關係元件維護
  • 靈活書寫動態 SQL,支援各種條件來動態生成不同的 SQL

缺點

  • 編寫 SQL 語句時工作量很大,尤其是欄位多、關聯表多時,更是如此
  • SQL 語句依賴於資料庫,導致資料庫移植性差

MyBatis 幾個重要的概念

Mapper 配置可以使用基於 XML 的 Mapper 配置檔案來實現,也可以使用基於 Java 註解的 MyBatis 註解來實現,甚至可以直接使用 MyBatis 提供的 API 來實現。

Mapper 介面是指自行定義的一個數據操作介面,類似於通常所說的 DAO 介面。早期的 Mapper 介面需要自定義去實現,現在 MyBatis 會自動為 Mapper 介面建立動態代理物件。Mapper 介面的方法通常與 Mapper 配置檔案中的 select、insert、update、delete 等 XML 結點存在一一對應關係。

Executor,MyBatis 中所有的 Mapper 語句的執行都是通過 Executor 進行的,Executor 是 MyBatis 的一個核心介面。

SqlSession,是 MyBatis 的關鍵物件,是執行持久化操作的獨享,類似於 JDBC 中的 Connection,SqlSession 物件完全包含以資料庫為背景的所有執行 SQL 操作的方法,它的底層封裝了 JDBC 連線,可以用 SqlSession 例項來直接執行被對映的 SQL 語句。

SqlSessionFactory,是 MyBatis 的關鍵物件,它是單個數據庫對映關係經過編譯後的記憶體映象。SqlSessionFactory 物件的例項可以通過 SqlSessionFactoryBuilder 物件類獲得,而 SqlSessionFactoryBuilder 則可以從 XML 配置檔案或一個預先定製的 Configuration 的例項構建出。

MyBatis 的工作流程如下:

  • 首先載入 Mapper 配置的 SQL 對映檔案,或者是註解的相關 SQL 內容。
  • 建立會話工廠,MyBatis 通過讀取配置檔案的資訊來構造出會話工廠(SqlSessionFactory)。
  • 建立會話。根據會話工廠,MyBatis 就可以通過它來建立會話物件(SqlSession),會話物件是一個介面,該介面中包含了對資料庫操作的增、刪、改、查方法。
  • 建立執行器。因為會話物件本身不能直接操作資料庫,所以它使用了一個叫做資料庫執行器(Executor)的介面來幫它執行操作。
  • 封裝 SQL 物件。在這一步,執行器將待處理的 SQL 資訊封裝到一個物件中(MappedStatement),該物件包括 SQL 語句、輸入引數對映資訊(Java 簡單型別、HashMap 或 POJO)和輸出結果對映資訊(Java 簡單型別、HashMap 或 POJO)。
  • 操作資料庫。擁有了執行器和 SQL 資訊封裝物件就使用它們訪問資料庫了,最後再返回操作結果,結束流程。

在我們具體的使用過程中,就是按照上述的流程來執行。

什麼是 MyBatis-Spring-Boot-Starter

mybatis-spring-boot-starter 是 MyBatis 幫助我們快速整合 Spring Boot 提供的一個元件包,使用這個元件可以做到以下幾點:

  • 構建獨立的應用
  • 幾乎可以零配置
  • 需要很少的 XML 配置

mybatis-spring-boot-starter 依賴於 MyBatis-Spring 和 Spring Boot,最新版 1.3.2 需要 MyBatis-Spring 1.3 以上,Spring Boot 版本 1.5 以上。

注意 mybatis-spring-boot-starter 是 MyBatis 官方開發的 Starter,而不是 Spring Boot 官方開發的啟動包,其實是 MyBatis 看 Spring Boot 市場使用度非常高,因此主動開發出 Starter 包進行整合,但這一整合確實解決了很多問題,使用起來比以前簡單很多。mybatis-spring-boot-starter 主要提供了兩種解決方案,一種是簡化後的 XML 配置版,一種是使用註解解決一切問題。

MyBatis 以前只有 XML 配置這種使用的形式,到了後來註解使用特別廣泛,MyBatis 也順應潮流提供了註解的支援,從這裡可以看出 MyBatis 一直都跟隨著主流技術的變化來完善自己。接下來給大家介紹一下如何使用 XML 版本。

XML 版本保持對映檔案的方式,最新版的使用主要體現在不需要實現 Dao 的實現層,系統會自動根據方法名在對映檔案中找到對應的 SQL。

初始化指令碼

為了方便專案演示,需要在 test 倉庫建立 users 表,指令碼如下:

DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `userName` varchar(32) DEFAULT NULL COMMENT '使用者名稱',
  `passWord` varchar(32) DEFAULT NULL COMMENT '密碼',
  `user_sex` varchar(32) DEFAULT NULL,
  `nick_name` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

關鍵依賴包

當然任何模式都需要首先引入 mybatis-spring-boot-starter 的 pom 檔案,現在最新版本是 1.3.2。

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>

application 配置

application.properties 新增相關配置:

mybatis.config-location=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
mybatis.type-aliases-package=com.neo.model

spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

其中:

  • mybatis.config-location,配置 mybatis-config.xml 路徑,mybatis-config.xml 中配置 MyBatis 基礎屬性;
  • mybatis.mapper-locations,配置 Mapper 對應的 XML 檔案路徑;
  • mybatis.type-aliases-package,配置專案中實體類包路徑;
  • spring.datasource.*,資料來源配置。

Spring Boot 啟動時資料來源會自動注入到 SqlSessionFactory 中,使用 SqlSessionFactory 構建 SqlSessionFactory,再自動注入到 Mapper 中,最後我們直接使用 Mapper 即可。

啟動類

在啟動類中新增對 Mapper 包掃描 @MapperScan,Spring Boot 啟動的時候會自動載入包路徑下的 Mapper。

@Spring BootApplication
@MapperScan("com.neo.mapper")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

或者直接在 Mapper 類上面添加註解 @Mapper,建議使用上面那種,不然每個 mapper 加個註解也挺麻煩的。

示例演示

MyBatis 公共屬性

mybatis-config.xml 主要配置常用的 typeAliases,設定類型別名,為 Java 型別設定一個短的名字。它只和 XML 配置有關,存在的意義僅在於用來減少類完全限定名的冗餘。

<configuration>
    <typeAliases>
        <typeAlias alias="Integer" type="java.lang.Integer" />
        <typeAlias alias="Long" type="java.lang.Long" />
        <typeAlias alias="HashMap" type="java.util.HashMap" />
        <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
        <typeAlias alias="ArrayList" type="java.util.ArrayList" />
        <typeAlias alias="LinkedList" type="java.util.LinkedList" />
    </typeAliases>
</configuration>

這樣我們在使用 Mapper.xml 的時候,需要引入可以直接這樣寫:

resultType="Integer" 
//或者
parameterType="Long"

新增 User 的對映檔案

第一步,指明對應檔案的 Mapper 類地址:

<mapper namespace="com.neo.mapper.UserMapper" >

第二部,配置表結構和類的對應關係:

<resultMap id="BaseResultMap" type="com.neo.model.User" >
    <id column="id" property="id" jdbcType="BIGINT" />
    <result column="userName" property="userName" jdbcType="VARCHAR" />
    <result column="passWord" property="passWord" jdbcType="VARCHAR" />
    <result column="user_sex" property="userSex" javaType="com.neo.enums.UserSexEnum"/>
    <result column="nick_name" property="nickName" jdbcType="VARCHAR" />
</resultMap>

這裡為了更好的貼近工作情況,將類的兩個欄位和資料庫欄位設定為不一致,其中一個使用了列舉。使用列舉有一個非常大的優點,插入此屬性的資料會自動進行校驗,如果不是列舉的內容會報錯。

第三步,寫具體的 SQL 語句,比如這樣:

<select id="getAll" resultMap="BaseResultMap"  >
   SELECT 
   *
   FROM users
</select>

MyBatis XML 有一個特點是可以複用 XML,比如我們公用的一些 XML 片段可以提取出來,在其他 SQL 中去引用。例如:

<sql id="Base_Column_List" >
    id, userName, passWord, user_sex, nick_name
</sql>

<select id="getAll" resultMap="BaseResultMap"  >
   SELECT 
   <include refid="Base_Column_List" />
   FROM users
</select>  

這個例子就是,上面定義了需要查詢的表字段,下面 SQL 使用 include 引入,避免了寫太多重複的配置內容。

下面是常用的增、刪、改、查的例子:

<select id="getOne" parameterType="Long" resultMap="BaseResultMap" >
    SELECT 
   <include refid="Base_Column_List" />
   FROM users
   WHERE id = #{id}
</select>

<insert id="insert" parameterType="com.neo.model.User" >
   INSERT INTO 
           users
           (userName,passWord,user_sex) 
       VALUES
           (#{userName}, #{passWord}, #{userSex})
</insert>

<update id="update" parameterType="com.neo.model.User" >
   UPDATE 
           users 
   SET 
       <if test="userName != null">userName = #{userName},</if>
       <if test="passWord != null">passWord = #{passWord},</if>
       nick_name = #{nickName}
   WHERE 
           id = #{id}
</update>

<delete id="delete" parameterType="Long" >
   DELETE FROM
            users 
   WHERE 
            id =#{id}
</delete>

上面 update 的 SQL 使用了 if 標籤,可以根據不同的條件生產動態 SQL,這就是 MyBatis 最大的特點。

編寫 Dao 層的程式碼

public interface UserMapper {

    List<UserEntity> getAll();

    UserEntity getOne(Long id);

    void insert(UserEntity user);

    void update(UserEntity user);

    void delete(Long id);
}

注意:這裡的方法名需要和 XML 配置中的 id 屬性一致,不然會找不到方法去對應執行的 SQL。

測試使用

按照 Spring 一貫使用形式,直接將對應的 Mapper 注入即可。

@Resource
private UserMapper userMapper;

如果使用的是 Idea,這塊的註解經常會報“could not autowire”,Eclipse 卻沒有問題,其實程式碼是正確的,這是 Idea 的誤報。可以選擇降低 Autowired 檢測的級別,不要提示就好。

在 File | Settings | Editor | Inspections 選項中使用搜索功能找到 Autowiring for Bean Class,將 Severity 的級別由之前的 error 改成 warning 即可。

接下來直接使用 userMapper 進行資料庫操作即可。

@Test
public void testUser()  {
    //增加
    userMapper.insert(new User("aa", "a123456", UserSexEnum.MAN));
    //刪除
    int count=userMapper.delete(2l);
    User user = userMapper.getOne(1l);
    user.setNickName("smile");
    //修改
    userMapper.update(user);
    //查詢
    List<User> users = userMapper.getAll();
}

在示例程式碼中,寫了兩份的使用示例,一個是 Test,一個在 Controller 層,方便大家下載檢視。

分頁查詢

多條件分頁查詢是實際工作中最常使用的功能之一,MyBatis 特別擅長處理這類的問題。在實際工作中,會對分頁進行簡單的封裝,方便前端使用。另外在 Web 開發規範使用中,Web 層的引數會以 param 為字尾的物件進行傳參,以 result 結尾的實體類封裝返回的資料。

下面給大家以 User 多條件分頁查詢為例進行講解。

先定義一個分頁的基礎類:

public class PageParam {
    private int beginLine;       //起始行
    private Integer pageSize = 3;
    private Integer currentPage=0;        // 當前頁
    //getter setter省略
    public int getBeginLine() {
        return pageSize*currentPage;//自動計算起始行
    }
}

預設每頁 3 條記錄,可以根據前端傳參進行修改。

user 的查詢條件引數類繼承分頁基礎類:

public class UserParam extends PageParam{
    private String userName;
    private String userSex;
    //getter setter省略
}

接下來配置具體的 SQL,先將查詢條件提取出來。

<sql id="Base_Where_List">
    <if test="userName != null  and userName != ''">
        and userName = #{userName}
    </if>
    <if test="userSex != null and userSex != ''">
        and user_sex = #{userSex}
    </if>
</sql>

從物件 UserParam 中獲取分頁資訊和查詢條件,最後進行組合。

<select id="getList" resultMap="BaseResultMap" parameterType="com.neo.param.UserParam">
    select
    <include refid="Base_Column_List" />
    from users
    where 1=1
    <include refid="Base_Where_List" />
    order by id desc
    limit #{beginLine} , #{pageSize}
</select>

前端需要展示總共的頁碼,因此需要統計出查詢結果的總數。

<select id="getCount" resultType="Integer" parameterType="com.neo.param.UserParam">
    select
    count(1)
    from users
    where 1=1
    <include refid="Base_Where_List" />
</select>

Mapper 中定義的兩個方法和配置檔案相互對應。

public interface UserMapper {
    List<UserEntity> getList(UserParam userParam);
    int getCount(UserParam userParam);
}

具體使用:

@Test
public void testPage() {
    UserParam userParam=new UserParam();
    userParam.setUserSex("WOMAN");
    userParam.setCurrentPage(1);
    List<UserEntity> users=userMapper.getList(userParam);
    long count=userMapper.getCount(userParam);
    Page page = new Page(userParam,count,users);
    System.out.println(page);
}

在實際使用中,只需要傳入 CurrentPage 引數即可,預設 0 就是第一頁,傳 1 就是第二頁的內容,最後將結果封裝為 Page 返回給前端。

public class Page<E> implements Serializable {
    private int currentPage = 0; //當前頁數
    private long totalPage;       //總頁數
    private long totalNumber;    //總記錄數
    private List<E> list;        //資料集
}

Page 將分頁資訊和資料資訊進行封裝,方便前端顯示第幾頁、總條數和資料,這樣分頁功能就完成了。

多資料來源處理

接下來為大家介紹如何使用 MyBatis 配置多資料來源使用。

配置檔案

首先我們需要配置兩個不同的資料來源:

mybatis.config-location=classpath:mybatis/mybatis-config.xml

spring.datasource.one.jdbc-url=jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.one.username=root
spring.datasource.one.password=root
spring.datasource.one.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.two.jdbc-url=jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.two.username=root
spring.datasource.two.password=root
spring.datasource.two.driver-class-name=com.mysql.cj.jdbc.Driver

注意,需要提前在 test1 和 test2 庫中建立好 User 表結構。

第一個資料來源以 spring.datasource.one.* 為字首連線資料庫 test1,第二個資料來源以 spring.datasource.two.* 為字首連線資料庫 test2。

同時需要將上述的 UserMapper.xml 檔案複製兩份到 resources/mybatis/mapper/one 和 resources/mybatis/mapper/two 目錄下各一份。

資料來源配置

為兩個資料來源建立不同的 Mapper 包路徑,將以前的 UserMapper 複製到包 com.neo.mapper.one 和 com.neo.mapper.two 路徑下,並且分別重新命名為:User1Mapper、User2Mapper。

配置第一個資料來源,新建 DataSource1Config。

首先載入配置的資料來源:

@Bean(name = "oneDataSource")
@ConfigurationProperties(prefix = "spring.datasource.one")
@Primary
public DataSource testDataSource() {
    return DataSourceBuilder.create().build();
}

注意,在多資料來源中只能指定一個 @Primary 作為預設的資料來源使用。

根據建立的資料來源,構建對應的 SqlSessionFactory。

@Bean(name = "oneSqlSessionFactory")
@Primary
public SqlSessionFactory testSqlSessionFactory(@Qualifier("oneDataSource") DataSource dataSource) throws Exception {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/one/*.xml"));
    return bean.getObject();
}

程式碼中需要指明需要載入的 Mapper xml 檔案。

同時將資料來源新增到事務中。

@Bean(name = "oneTransactionManager")
@Primary
public DataSourceTransactionManager testTransactionManager(@Qualifier("oneDataSource") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

接下來將上面建立的 SqlSessionFactory 注入,建立我們在 Mapper 中需要使用的 SqlSessionTemplate。

@Bean(name = "oneSqlSessionTemplate")
@Primary
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("oneSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
    return new SqlSessionTemplate(sqlSessionFactory);
}

最後將上面建立的 SqlSessionTemplate 注入到對應的 Mapper 包路徑下,這樣這個包下面的 Mapper 都會使用第一個資料來源來進行資料庫操作。

@Configuration
@MapperScan(basePackages = "com.neo.mapper.one", sqlSessionTemplateRef  = "oneSqlSessionTemplate")
public class OneDataSourceConfig {
 ...
}
  • basePackages 指明 Mapper 地址。
  • sqlSessionTemplateRef 指定 Mapper 路徑下注入的 sqlSessionTemplate。

第二個資料來源配置

DataSource2Config 的配置和上面類似,方法上需要去掉 @Primary 註解,替換對應的資料來源和 Mapper 路徑即可。下面是 DataSource2Config 完整示例:

@Configuration
@MapperScan(basePackages = "com.neo.mapper.two", sqlSessionTemplateRef  = "twoSqlSessionTemplate")
public class DataSource2Config {

    @Bean(name = "twoDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.two")
    public DataSource testDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "twoSqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("twoDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/two/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "twoTransactionManager")
    public DataSourceTransactionManager testTransactionManager(@Qualifier("twoDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "twoSqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("twoSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

從上面的步驟我們可以總結出來,建立多資料來源的過程就是:首先建立 DataSource,注入到 SqlSessionFactory 中,再建立事務,將 SqlSessionFactory 注入到建立的 SqlSessionTemplate 中,最後將 SqlSessionTemplate 注入到對應的 Mapper 包路徑下。其中需要指定分庫的 Mapper 包路徑。

注意,在多資料來源的情況下,我們不需要在啟動類新增:@MapperScan("com.xxx.mapper") 的註解。

這樣 MyBatis 多資料來源的配置就完成了,如果有更多的資料來源請參考第二個資料來源的配置即可。

測試

配置好多資料來源之後,在專案中想使用哪個資料來源就把對應資料來源注入到類中使用即可。

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest {
    @Autowired
    private User1Mapper user1Mapper;
    @Autowired
    private User2Mapper user2Mapper;

    @Test
    public void testInsert() throws Exception {
        user1Mapper.insert(new User("aa111", "a123456", UserSexEnum.MAN));
        user1Mapper.insert(new User("bb111", "b123456", UserSexEnum.WOMAN));
        user2Mapper.insert(new User("cc222", "b123456", UserSexEnum.MAN));
    }
}

上面的測試類中注入了兩個不同的 Mapper,對應了不同的資料來源。在第一個資料來源中插入了兩條資料,第二個資料來源中插入了一條資訊,執行測試方法後檢視資料庫1有兩條資料,資料庫2有一條資料,證明多資料來源測試成功。

總結

這節課介紹了 ORM 框架 和 MyBatis 框架相關概念介紹,以使用者資料為例演示了 MyBatis 的增、刪、改、查,以及分頁查詢、多資料來源處理等常見場景。通過上面的示例可以發現 MyBatis 將執行 SQL 和程式碼做了隔離,保證程式碼處理和 SQL 的相對獨立,層級劃分比較清晰,MyBatis 對動態 SQL 支援非常友好,可以在 XML 檔案中複用程式碼高效編寫動態 SQL。