banner
jzman

jzman

Coding、思考、自觉。
github

Spring Boot系列MyBatis配置詳解

PS:你的狀態取決於你的心態,要想不再焦慮,先把生活節奏規律起來。

前面幾篇文章嘗試了介面開發、Thymeleaf 模板、常用語法、模板佈局、專案國際化、JDBC 等,閱讀本文之前可以閱讀前面幾篇文章:

MyBatis 是一款優秀的持久層框架,MyBatis 使用 XML 或者註解來進行配置和映射,可以方便的將 POJO 映射成資料庫中的記錄。

  1. MyBatis 工作流程
  2. 依賴及配置
  3. @Mapper@MapperScan
  4. 實體類
  5. Mapper 配置文件
  6. Mapper 接口
  7. Mapper 映射文件
  8. collection 標籤的使用
  9. 多資料源配置
  10. 測試結果
  11. MyBatis 使用註解配置

MyBatis 工作流程#

MyBatis 工作流程如下圖所示:

image

  1. 讀取 mybatis-config.xml 配置文件;
  2. 加載 Mapper 映射文件或對應註解內容,裡面定義了相應的 SQL 語句;
  3. 根據配置信息創建會話工廠 SqlSessionFactory
  4. 根據會話工廠創建 SqlSession,裡面包含了執行 SQL 需要的所有方法;
  5. 創建 Executor 執行器,用來執行 SQL 語句,在創建會話工廠 SqlSessionFactory 的時候就會創建一個 Executor, 其默認執行器類型是 ExecutorType.SIMPLE
  6. MappedStatement 對象,該對象是 Executor 執行器方法中的參數,主要是對 Mapper XML 文件中映射信息的封裝;
  7. 輸入參數映射;
  8. 輸出參數映射。

依賴及配置#

創建 Spring Boot 專案,在其 build.gradle 文件中添加 MyBatis 和 MySQL 驅動的依賴如下:

dependencies {
    // ...
    // myBaits
    // http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/index.html
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'
    // mysql驅動
    runtime("mysql:mysql-connector-java")
    // ...
}

然後在專案的 application.properties 文件中配置資料庫連接參數以及 MyBatis 相關配置,如下:

# 資料庫用戶名
spring.datasource.username=root
# 資料庫密碼
spring.datasource.password=admin
# JDBC Driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JDBC URL
spring.datasource.url=jdbc:mysql://localhost:3306/db_student?serverTimezone=Asia/Shanghai
#spring.datasource.url=jdbc:mysql://localhost:3306/db_student?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true

# 是否執行MyBatis xml配置文件的狀態檢查, 只是檢查狀態,默認false
mybatis.check-config-location=true
# mybatis-config.xml文件的位置
mybatis.config-location=classpath:mybatis/mybatis-config.xml
# Mapper對應的xml路徑
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
# 設置別名的路徑,可避免寫全限定類名
mybatis.type-aliases-package=com.manu.mybatisxml.model

MyBatis 主要配置的是配置文件 mybatis-config.xml 的路徑、Mapper 對應的 XML 文件的路徑。

@Mapper 和 @MapperScan#

@Mapper 註解用來標記 Mapper 接口,其被標註的接口都會生成對應的動態代理類,如果有多個 Mapper 接口,則都需要用 @Mapper 註解來標註,使用方式如下:

@Mapper
public interface ClassMapper{
    ///
}

@MapperScan 在專案的入口類上進行標註,可以配置要掃描的接口所在的一個或多個包,也可以使用通配符 * 來進行配置,使用方式如下:

@SpringBootApplication
// 掃描指定包中的接口
@MapperScan("com.manu.mybatisxml.mapper")
// @MapperScan("com.manu.mybatisxml.*.mapper")
// @MapperScan({"pack1","pack2"})
public class SpringBootMybatisXmlApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootMybatisXmlApplication.class, args);
    }
}

實體類#

案例是班級和學生的關係,也就是一對多的關係,定義班級類 Class 如下:

/**
 * 班級類
 */
public class Class {
    private String classId;
    private String name;
    private List<Student> students;
    public Class() {
    }
    public Class(String classId, String name) {
        this.classId = classId;
        this.name = name;
    }
    // ...
    // setter、getter、toString
}

定義學生類 Student 類如下:

/**
 * 學生類
 */
public class Student {
    private String classId;
    private String sno;
    private String name;
    private String grade;
    public Student() {
    }
    public Student(String classId, String sno, String name, String grade) {
        this.classId = classId;
        this.sno = sno;
        this.name = name;
        this.grade = grade;
    }
    // ...
    // setter、getter、toString
}

MyBatis 配置文件#

MyBatis 的配置文件是 mybatis-config.xml 文件,當在 Spring Boot 中使用 MyBatis 時,該配置文件中的大多數配置都可以在 application.properties 文件中配置,所以在 Spring Boot 專案中可以借助該配置文件簡化全限定類名,如下:

<?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>
    <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" />
        <typeAlias alias="Student" type="com.manu.mybatisxml.model.Student" />
        <typeAlias alias="Class" type="com.manu.mybatisxml.model.Class" />
    </typeAliases>
</configuration>

Mapper 接口#

Mapper 接口中中方法名對應 Mapper 映射文件中對應的 SQL 語句對應的方法,且方法名必須與對應的 SQL 語句中的 id 屬性子相同,ClassMapper 如下:

/**
 * ClassMapper.xml對應的Mapper接口
 */
public interface ClassMapper {
    /**
     * 插入一條數據
     * @param student student
     */
    void insertStudent(Student student);
    void insertClass(Class course);
    /**
     * 根據sno刪除一條記錄
     * @param sno sno
     */
    void deleteStudent(String sno);
    /**
     * 更新數據
     * @param student student
     */
    void updateStudent(Student student);
    /**
     * 更具名稱查詢數據
     * @param name name
     * @return
     */
    Student findStudentByName(String name);
    /**
     * 查詢全部數據
     * @return
     */
    List<Student> findAllStudent();
    /**
     * 集合數據查詢
     * @param name name
     * @return
     */
    Class findClassStudents(String name);
    /**
     * 集合數據嵌套查詢
     * @param classId classId
     * @return
     */
    Class findClassStudents1(String classId);
}

Mapper 映射文件#

Mapper 映射文件以 XML 為基礎,使用與 SQL 語句對應的 SQL 標籤來靈活的構建 SQL 語句,一些標籤及其屬性都是見名知意,常用標籤如下:

  • mapper :配置 Mapper 映射文件對應的 Mapper 接口類;
  • resultMap:查詢語句結果集;
  • result:用於定義 resultMap 標籤中的字段;
  • id:用於定義 resultMap 標籤中的主鍵字段;
  • collection:集合數據,如 List<Student> 這種數據;
  • sql:定義 SQL 語句塊供其他 SQL 語句使用;
  • insert:插入語句;
  • delete:刪除語句;
  • update:更新語句;
  • select:查詢語句。

常用屬性可查看如下案例中的相關註釋,上述 Mapper 接口類 ClassMapper 對應的 Mapper 映射文件如下:

<?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.manu.mybatisxml.mapper.ClassMapper">
    <!--Student POJO映射結果集-->
    <!--id:唯一標識-->
    <!--type:具體的POJO對象類型-->
    <resultMap id="StudentResultMap" type="com.manu.mybatisxml.model.Student">
        <!--column:主鍵字段也可以是查詢語句中的別名字段-->
        <!--property:對應POJO對象中的屬性-->
        <!--jdbcType:字段類型-->
        <id column="classId" property="classId" jdbcType="VARCHAR" />
        <!--column:表的字段-->
        <result column="userName" property="name" jdbcType="VARCHAR" />
        <result column="sno" property="sno" jdbcType="VARCHAR" />
        <result column="grade" property="grade" jdbcType="VARCHAR" />
    </resultMap>

    <!--Student POJO映射結果集,攜帶集合結果集-->
    <resultMap id="ClassWithCollectionResultMap" type="com.manu.mybatisxml.model.Class">
        <id column="classId" property="classId" jdbcType="VARCHAR" />
        <result column="name" property="name" jdbcType="VARCHAR" />
        <!--ofType:集合中的數據類型-->
        <collection property="students" ofType="Student">
            <id column="sno" property="sno" jdbcType="VARCHAR" />
            <result column="userName" property="name" jdbcType="VARCHAR" />
            <result column="classId" property="classId" jdbcType="VARCHAR" />
            <result column="grade" property="grade" jdbcType="VARCHAR" />
        </collection>
    </resultMap>

    <!--Student POJO映射結果集,攜帶集合結果集,嵌套查詢-->
    <resultMap id="ClassWithCollectionResultMap1" type="com.manu.mybatisxml.model.Class">
        <id column="classId" property="classId" jdbcType="VARCHAR" />
        <result column="name" property="name" jdbcType="VARCHAR" />
        <!--column:嵌套查詢的條件-->
        <!--select:嵌套查詢的語句-->
        <collection column="{classId = classId}" property="students" ofType="Student"
            select="getStudent" />
    </resultMap>

    <select id="getStudent" parameterType="String" resultMap="StudentResultMap">
        SELECT *
        FROM mybatis_student
        WHERE classId = #{classId}
    </select>

    <!--定義基本字段-->
    <sql id="BaseStudentColumn">
        sno,userName,classId,grade
    </sql>

    <!--插入數據-->
    <!--id標識對應Mapper接口中的方法名稱-->
    <insert id="insertClass" parameterType="Class">
        INSERT INTO mybatis_class(classId, name)
        VALUES (#{classId}, #{name})
    </insert>
    <insert id="insertStudent" parameterType="Student">
        INSERT INTO mybatis_student(classId, userName, sno, grade)
        VALUES (#{classId}, #{name}, #{sno}, #{grade})
    </insert>

    <!--刪除數據-->
    <delete id="deleteStudent" parameterType="String">
        DELETE
        FROM mybatis_student
        WHERE sno = #{sno}
    </delete>

    <!--更新數據-->
    <update id="updateStudent" parameterType="Student">
        UPDATE mybatis_student
        SET userName = #{name},
            classId  = #{classId},
            grade    = #{grade},
            sno      = #{sno}
        WHERE sno = #{sno}
    </update>

    <!--查詢滿足條件的數據集合-->
    <select id="findClassStudents" parameterType="String" resultMap="ClassWithCollectionResultMap">
        SELECT mybatis_class.classId,
               mybatis_class.name,
               mybatis_student.sno,
               mybatis_student.userName,
               mybatis_student.grade
        FROM mybatis_student,
             mybatis_class
        WHERE mybatis_class.classId = mybatis_student.classId
          and mybatis_class.name = #{name}
    </select>

    <!--查詢滿足條件的數據集合-->
    <select id="findClassStudents1" parameterType="String"
        resultMap="ClassWithCollectionResultMap1">
        SELECT mybatis_class.classId,
               mybatis_class.name,
               mybatis_student.sno,
               mybatis_student.userName,
               mybatis_student.grade
        FROM mybatis_student,
             mybatis_class
        WHERE mybatis_class.classId = mybatis_student.classId
          and mybatis_class.classId = #{classId}
    </select>

    <!--查詢單個數據-->
    <select id="findStudentByName" resultMap="StudentResultMap" parameterType="String">
        SELECT *
        FROM mybatis_student
        WHERE userName = #{name}
    </select>

    <!--查詢全部數據-->
    <select id="findAllStudent" resultMap="StudentResultMap">
        SELECT
        <include refid="BaseStudentColumn" />
        FROM mybatis_student
    </select>
</mapper>

collection 標籤的使用#

上文介紹了 Mapper 文件中中的一些常用標籤,其他標籤的使用沒什麼可單獨說的,這裡單獨說明一下 <collection/> 標籤的使用,該標籤主要用來標識結果集,如班級類 Class 中的學生集合 List<Student>,通過該標籤就可以查詢到指定班級的學生集合,第一種方式:

<!--Student POJO映射結果集,攜帶集合結果集-->
<resultMap id="ClassWithCollectionResultMap" type="Class">
    <id column="classId" property="classId" jdbcType="VARCHAR" />
    <result column="name" property="name" jdbcType="VARCHAR" />
    <!--ofType:集合中的數據類型-->
    <collection property="students" ofType="Student">
        <id column="sno" property="sno" jdbcType="VARCHAR" />
        <result column="userName" property="name" jdbcType="VARCHAR" />
        <result column="classId" property="classId" jdbcType="VARCHAR" />
        <result column="grade" property="grade" jdbcType="VARCHAR" />
    </collection>
</resultMap>

對應的查詢 SQL 映射如下:

<!--查詢滿足條件的數據集合-->
<select id="findClassStudents" parameterType="String" resultMap="ClassWithCollectionResultMap">
    SELECT mybatis_class.classId,
           mybatis_class.name,
           mybatis_student.sno,
           mybatis_student.userName,
           mybatis_student.grade
    FROM mybatis_student,
         mybatis_class
    WHERE mybatis_class.classId = mybatis_student.classId
      and mybatis_class.name = #{name}
</select>

第二種方式如下:

<!--Student POJO映射結果集,攜帶集合結果集,嵌套查詢-->
<resultMap id="ClassWithCollectionResultMap1" type="com.manu.mybatisxml.model.Class">
    <id column="classId" property="classId" jdbcType="VARCHAR" />
    <result column="name" property="name" jdbcType="VARCHAR" />
    <!--column:嵌套查詢的條件-->
    <!--select:嵌套查詢的語句-->
    <collection column="{classId = classId}" property="students" ofType="Student"
        select="getStudent" />
</resultMap>

<select id="getStudent" parameterType="String" resultMap="StudentResultMap">
    SELECT *
    FROM mybatis_student
    WHERE classId = #{classId}
</select>

對應的查詢 SQL 映射如下:

<!--查詢滿足條件的數據集合-->
<select id="findClassStudents1" parameterType="String"
    resultMap="ClassWithCollectionResultMap1">
    SELECT mybatis_class.classId,
           mybatis_class.name,
           mybatis_student.sno,
           mybatis_student.userName,
           mybatis_student.grade
    FROM mybatis_student,
         mybatis_class
    WHERE mybatis_class.classId = mybatis_student.classId
      and mybatis_class.classId = #{classId}
</select>

通過在 Mapper 接口中定義的 findClassStudents 即可查詢到 Student 的對應集合。

多資料源配置#

分別創建多資料源配置文件,生成多個不同的資料源以及不同的 SqlSessionFactory 等,主要資料源配置如下:

/**
 * @Primary表示主資料源
 * basePackages:指定掃描的Mapper接口
 * sqlSessionTemplateRef:指定在Mapper路徑下指定的SqlSessionTemplate
 */
@Configuration
@MapperScan(basePackages = "com.manu.multimybatisxml.mapper.primary",
        sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class PrimaryDataSourceConfig {

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean
    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        sessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/primary/*.xml"));
        return sessionFactoryBean.getObject();
    }

    @Primary
    @Bean
    public DataSourceTransactionManager primaryDataSourceTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Primary
    @Bean
    public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

第二個資料源的配置同上,只是不在標註 @Primary, 修改第二個資料源的名稱以及對應的 Mapper 映射文件等,這裡不再贅述。

然後按照上述配置中指定的前綴,在 application.properties 文件中配置多資料庫連接如下:

# dataSourceOne
spring.datasource.primary.username=root
spring.datasource.primary.password=admin
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.datasource.jdbc-url 多資料源中用來重寫自定義連接池
spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/data_source_one?serverTimezone=Asia/Shanghai

# dataSourceTwo
spring.datasource.secondary.username=root
spring.datasource.secondary.password=admin
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/data_source_two?serverTimezone=Asia/Shanghai

# 是否執行MyBatis xml配置文件的狀態檢查, 只是檢查狀態,默認false
mybatis.check-config-location=true
# mybatis-config.xml文件的位置
mybatis.config-location=classpath:mybatis/mybatis-config.xml
# 設置別名的路徑,可避免寫全限定類名
mybatis.type-aliases-package=com.manu.multimybatisxml.model

具體內容可回復關鍵字【Spring Boot】獲取源碼鏈接。

測試結果#

案例僅僅是為了說明使用方式,讀者無需關心其合理性,編寫測試類如下:

/**
 * MyBatisTest
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyBatisTest {
    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Autowired
    private ClassMapper mClassMapper;

    @Test
    public void insert() {
        Class class1 = new Class("class1", "一班");
        Class class2 = new Class("class2", "二班");
        mClassMapper.insertClass(class1);
        mClassMapper.insertClass(class2);

        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Student student;
            if (i % 2 == 0) {
                student = new Student("class1", "sno" + i, "Student"+i, "A");
            } else {
                student = new Student("class2", "sno" + i, "Student"+i, "B");
            }
            mClassMapper.insertStudent(student);
        }
    }

    @Test
    public void deleteStudentBySno() {
        mClassMapper.deleteStudent("sno0");
    }

    @Test
    public void updateStudent() {
        Student student = new Student("class1","sno1","student1","C");
        mClassMapper.updateStudent(student);
    }

    @Test
    public void findStudentByName() {
        Student student = mClassMapper.findStudentByName("student5");
        System.out.println(student);
    }

    @Test
    public void findAllStudent() {
        List<Student> students = mClassMapper.findAllStudent();
        for (Student student : students) {
            System.out.println(student.toString());
        }
    }

    @Test
    public void findClassStudents(){
        Class clazz = mClassMapper.findClassStudents("一班");
        System.out.println("classId:"+clazz.getClassId()+",name:"+clazz.getName());

        List<Student> students = clazz.getStudents();
        for (Student student : students) {
            System.out.println(student.toString());
        }
    }

    @Test
    public void findClassStudents1(){
        Class clazz = mClassMapper.findClassStudents1("class1");
        System.out.println("classId:"+clazz.getClassId()+",name:"+clazz.getName());

        List<Student> students = clazz.getStudents();
        for (Student student : students) {
            System.out.println(student.toString());
        }
    }
}

這裡以 findClassStudents 方法為例查看執行結果如下:

classId:class1,name:一班
Student{classId='class1', sno='sno1', name='student1', grade='C'}
Student{classId='class1', sno='sno2', name='Student2', grade='A'}
Student{classId='class1', sno='sno4', name='Student4', grade='A'}
Student{classId='class1', sno='sno6', name='Student6', grade='A'}
Student{classId='class1', sno='sno8', name='Student8', grade='A'}

註解配置#

MyBatis 除了使用 XML 進行配置外還可以通過註解進行配置,如下:

@Mapper
public interface StudentMapper {
    /**
     * 註解中的SQL語句中會自動獲取對象student的相關屬性
     */
    @Insert("INSERT INTO mybatis_student(userName,sno,grade) VALUES(#{name},#{sno},#{grade})")
    void insert(Student student);

    /**
     * StudentFactory中會自動獲取對象student的相關屬性在SQL語句中
     * StudentFactory中insert2方法通過#{屬性名}的方式獲取變量值
     */
    @InsertProvider(type = StudentFactory.class, method = "insert1")
    void insert1(Student student);

    /**
     * 直接傳遞參數
     * StudentFactory中insert2方法通過#{變量名}的方式獲取變量值
     * 此外也可以通過StringBuffer拼接SQL,如在insert2方法中拼接SQL字符串返回
     */
    @InsertProvider(type = StudentFactory.class, method = "insert2")
    void insert2(String sno, String name, String grade);
}

實現上述方法即可,如下:

public class StudentFactory {
    public String insert1(Student student) {
        String sql = new SQL() {{
            INSERT_INTO("mybatis_student");
            VALUES("sno", "#{sno}");
            VALUES("userName", "#{name}");
            VALUES("grade", "#{grade}");
        }}.toString();
        System.out.println("SQL:" + sql);
        return sql;
    }

    public String insert2(String sno,String name,String grade) {
        String sql = new SQL() {{
            INSERT_INTO("mybatis_student");
            VALUES("sno", "#{sno}");
            VALUES("userName", "#{name}");
            VALUES("grade", "#{grade}");
        }}.toString();
        System.out.println("SQL:" + sql);
        return sql;
    }
}

最後,進行測試,如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyBatisAnnotationTests {

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Autowired
    StudentMapper mStudentMapper;

    @Test
    public void insert() {
        Student student = new Student("sno0", "jzman0", "A");
        mStudentMapper.insert(student);
    }

    @Test
    public void insert1() {
        Student student = new Student("sno1", "jzman1", "A");
        mStudentMapper.insert1(student);
    }

    @Test
    public void insert2() {
        Student student = new Student("sno2", "jzman2", "A");
        mStudentMapper.insert2(student.getSno(), student.getName(), student.getGrade());
    }
}

MyBatis 使用註解方式代碼更少,但是在 SQL 的靈活性性上還是有一定的局限性,這裡沒實踐不做過多闡述。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。