天天看點

深入淺出springboot2.x(10)整合MyBatis

整合MyBatis架構

    應該說目前java持久層最為主流的技術已經是MyBatis,它比JPA和Hibernate更為簡單易用,也更加靈活。在以管理系統為主的時代,Hibernate的模型化有助于系統的分析和模組化,重點在于業務模型的分析和設計,屬于表和業務模型分析的階段。現在已經是移動網際網路時代,特點是面向公衆,相對而言業務比較簡單,但是往往網站會擁有大量的使用者,面對的問題主要是大資料、高并發和性能問題。是以在這個時代,網際網路企業開發的難度主要集中在大資料和性能問題上,是以網際網路企業更加關注的是系統的性能和靈活性。是以MyBatis應用非常廣泛。

MyBatis簡介

    MyBatis的官方定義為:MyBatis是支援定制化SQL、存儲過程以及進階映射的優秀的持久層架構。MyBatis避免了幾乎所有的JDBC代碼和手動設定參數以及擷取結果集。MyBatis可以對配置和原生Map使用簡單的xml或注解,将接口和java的pojo映射成資料庫中的記錄。

    從這個官方定義可以看出,MyBatis是基于一種SQL到pojo的模型,它需要我們提供SQL、映射關系(xml或者注解,目前以xml為主)和pojo。但是對于SQL和pojo的映射關系,它提供了自動映射和駝峰映射等,使開發者的開發工作大大減少;由于沒有屏蔽SQL,這對于追求高響應和性能的網際網路系統是十分重要的,是以我們可以盡可能地通過SQL去優化性能,也可以做少量的改變以适應靈活多變的網際網路應用。與此同時,它還能支援動态SQL,以适應需求的變化。

    MyBatis的配置檔案包括兩大部分,基礎配置檔案和映射檔案。在MyBatis中也可以使用注解來實作映射,隻是由于功能和可讀性的限制,在實際工作中使用的比較少。spring本身是不支援MyBatis的,是以在spring的項目中都沒有考慮MyBatis的整合。但是MyBatis社群為了整合spring自己開發了相應的開發包,是以在springboot中我們可以依賴MyBatis社群提供stater。在maven中加入依賴包:

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

MyBatis的配置

    MyBatis是一個基于SqlSessionFactory建構的架構。對于SqlSessionFactory而言,它的作用是生成SqlSession接口對象,這個接口對象是MyBatis操作的核心,而在MyBatis-spring的結合中甚至可以“擦除”這個對象,使其在代碼中“消失”,這樣做的意義是重大的,因為SqlSession是一個功能性的代碼,“擦除”它之後,就剩下了業務代碼,這樣就可以使得代碼更具可讀性。因為SqlSessionFactory的作用是單一的,隻是為了建立核心接口SqlSession,是以在MyBatis應用的生命周期中理當隻存在一個SqlSessionFactory對象,并且往往會使用單例模式。而建構SqlSessionFactory是通過配置類來完成的,是以對于mybatis-spring-boot-starter,它會給予我們在配置檔案進行Configuration配置的相關内容。

我們先來看一個簡單的例子:

實體類:User(對應的表建表語句可以在上一節開頭找)

package com.example.mybatisdemo;
import org.apache.ibatis.type.Alias;

@Alias(value="user")//MyBatis指定别名
public class User {
    private int id;
    private String userName;
    private SexEnum sex;
    private String note;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public SexEnum getSex() {
        return sex;
    }
    public void setSex(SexEnum sex) {
        this.sex = sex;
    }
    public String getNote() {
        return note;
    }
    public void setNote(String note) {
        this.note = note;
    }
}
           

    這裡加入了注解@Alias,并且指定它的别名“user”。同時這裡面的屬性多了一個性别枚舉,在mybatis體系中,枚舉是可以通過typeHandler進行轉換的。

枚舉類SexEnum:

package com.example.mybatisdemo;

public enum SexEnum {
    MALE(1,"男"),FEMALE(2,"女");
    private int id;
    private String name;
    SexEnum(int id, String name){
        this.id=id;
        this.name=name;
    }
    public static SexEnum getEnumById(int id){
        for(SexEnum sex: SexEnum.values()){
            if (sex.getId()==id){
                return sex;
            }
        }
        return null;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

           

轉換類SexTypeHandler:

package com.example.mybatisdemo;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class SexTypeHandler extends BaseTypeHandler<SexEnum> {
    //設定非空性别參數
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, SexEnum sexEnum, JdbcType jdbcType) throws SQLException {
        preparedStatement.setInt(i,sexEnum.getId());
    }
    //通過列名讀取性别
    @Override
    public SexEnum getNullableResult(ResultSet resultSet, String col) throws SQLException {
        int sex =resultSet.getInt(col);
        if(sex !=1 && sex!=2){
            return null;
        }
        return SexEnum.getEnumById(sex);
    }
    //通過下标讀取性别
    @Override
    public SexEnum getNullableResult(ResultSet resultSet, int i) throws SQLException {
        int sex = resultSet.getInt(i);
        if(sex !=1 && sex!=2){
            return null;
        }
        return SexEnum.getEnumById(sex);
    }
    //通過存儲過程讀取性别
    @Override
    public SexEnum getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        int sex = callableStatement.getInt(i);
        if(sex !=1 && sex!=2){
            return null;
        }
        return SexEnum.getEnumById(sex);
    }
}

           

在MyBatis中對于TypeHandler的要求是實作TypeHandler接口,而它自身為了更加友善也通過抽像類BaseTypeHandler實作了TypeHandler接口,是以這裡直接繼承抽像類BaseTypeHandler就可以了。注解@MappedJdbcTypes聲明JdbcType為資料庫的整型,@MappedTypes聲明JdbcType為SexEnum,這樣MyBatis即可将對應的資料類型進行轉換。為了使這個pojo能夠跟資料庫的資料對應,還需要提供一個映射檔案

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatisdemo.MyBatisUserDao">
    <select id="getUser" parameterType="integer" resultType="user">
        select id,user_name as userName,sex,note from t_user where id = #{id}
    </select>
</mapper>
           

映射檔案目錄結構:resources\mybatis\userMapper.xml

定義MyBatis操作接口:

package com.example.mybatisdemo;

import org.springframework.stereotype.Repository;

@Repository
public interface MyBatisUserDao {
    public User getUser(Integer id);
}
           

我們還需要對映射檔案,pojo的别名和typeHandler進行配置

#MyBatis映射檔案
mybatis.mapper-locations=classpath:mybatis/*.xml
#MyBatis掃描别名包,和注解@Alias聯用
mybatis.type-aliases-package=com.example.mybatisdemo
#配置typeHandler的掃描包
mybatis.type-handlers-package=com.example.mybatisdemo
           

Spring Boot整合MyBatis

    在大部分情況下,應該“擦除”SqlSession接口的使用而直接擷取Mapper接口,這樣就更加集中于業務的開發,而不是MyBatis功能性的開發。但是在上面我們可以看到Mapper是一個接口,是不可以使用new為期生成對象執行個體的。為了友善我們使用,MyBatis社群在與Spring整合的包中提供了兩個類,MapperFactoryBean和MapperScannerConfigure。MapperFactoryBean是針對一個接口配置,而MapperScannerConfigure則是掃描裝配,也就是提供掃描裝配MyBatis的接口到springioc容器中。實際上,MyBatis還提供了注解@MapperScan,也能夠将MyBatis所需的對應接口掃描裝配到springioc容器中。相對于MapperFactoryBean和MapperScannerConfigure這樣需要代碼開發的方式,@MapperScan顯得更為簡單,是以在大部分的情況下,建議使用這個。下面分别對這三個使用給與說明。

    使用MapperFactoryBean配置MyBatisUserDao接口,我們現在spring boot啟動類中加入這段代碼:

@Autowired
	SqlSessionFactory sqlSessionFactory;
	@Bean
	public MapperFactoryBean<MyBatisUserDao> initMyBatisUserDao(){
		MapperFactoryBean<MyBatisUserDao> bean = new MapperFactoryBean<>();
		bean.setMapperInterface(MyBatisUserDao.class);
		bean.setSqlSessionFactory(sqlSessionFactory);
		return bean;
	}
           

    這裡的SqlSessionFactory 是spring boot自動生成的,可以直接使用MapperFactoryBean定義Mapper接口。

    然後是服務接口和實作類:

package com.example.mybatisdemo;

public interface MyBatisUserService {
    public User getUser(Integer id);
}
           
package com.example.mybatisdemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MyBatisUserServiceImpl implements MyBatisUserService {
    @Autowired
    MyBatisUserDao myBatisUserDao;
    @Override
    public User getUser(Integer id) {
        return myBatisUserDao.getUser(id);
    }
}
           

    使用控制器測試:

package com.example.mybatisdemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/mybatis")
public class MyBatisController {
    @Autowired
    MyBatisUserService myBatisUserService;
    @RequestMapping("/getUser")
    @ResponseBody
    public User gerUser(Integer id){
        return myBatisUserService.getUser(id);
    }
}

           

    啟動spring boot應用,在浏覽器輸入http://localhost:8080/mybatis/getUser?id=1,就可以看到測試結果。需要在表裡面插入id=1的資料。

    使用MapperScannerConfigure類來定義掃描,它可以配置包和注解(或者接口)類型進行裝配,首先要删除spring boot啟動檔案中使用MapperFactoryBean配置MyBatisUserDao接口的代碼,然後加入MapperScannerConfigure掃描裝配MyBatis接口:

/**
	 * 配置mybatis接口掃描
	 * 傳回掃描器
	 */
	@Bean
	public MapperScannerConfigurer mapperScannerConfigurer(){
		//定義掃描器執行個體
		MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
		//加載SqlSessionFactory,spring boot會自動生成SqlSessionFactory執行個體
		mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
		//定義掃描的包
		mapperScannerConfigurer.setBasePackage("com.example");
		//限定被标注@Repository的接口才被掃描
		mapperScannerConfigurer.setAnnotationClass(Repository.class);
		//通過繼承某個接口限制掃描,一般使用不多
		//mapperScannerConfigurer.setMarkerInterface();
		return mapperScannerConfigurer;
	}
           

    上述代碼中使用MapperScannerConfigure類定義了掃描的包,這樣程式就會去掃描對應的包了。還是用了注解限制,限制被标注為Repository,這樣就防止在掃描中被錯誤裝配。

    實際上還有更為簡單的方式,那就是注解@MapperScan,把MapperFactoryBean和MapperScannerConfigure相關代碼删除,然後在啟動類上加入如下代碼:

@EnableJpaRepositories(basePackages = "com.example")
@EntityScan(basePackages = "com.example")
@SpringBootApplication
@ComponentScan({"com.example"})
@MapperScan(basePackages = "com.example.*",
		sqlSessionFactoryRef = "sqlSessionFactory",
		sqlSessionTemplateRef="sqlSessionTemplate",
		annotationClass = Repository.class)
public class JpAdemoApplication {
	.......
}
           

@MapperScan允許我們通過掃描加載MyBatis的Mapper,如果你的springboot項目中不存在多個SqlSessionFactory(或者SqlSessionTemplate),那麼你完全可以不配置sqlSessionFactoryRef 或sqlSessionTemplateRef。但是如果有多個時,就需要我們指定了,而且有一點需要注意的:sqlSessionTemplateRef的優先權是大于sqlSessionFactoryRef 的,也就是當我們将兩者都配置後,系統會優先選擇sqlSessionTemplateRef,而把sqlSessionFactoryRef 廢棄。

繼續閱讀