SpringBoot 和 MyBatis

前言

這一段時間在學習springboot,同時也就在嘗試着將以前使用過的很多東西和springboot進行整合。這次主要嘗試的是MyBatis的整合。本篇文章主要講的是mybatis和springboot的很多最基本的配置和用法,適合對springboot和mybatis有些熟悉的小白學習(因爲本身我也是一個新手)。如有一些沒有注意到的地方,或者是有錯誤的地方,還請大佬及時指出。

什麼是mybatis

MyBatis 是一個基於Java的持久層框架。2013年11月遷移到Github。
說白了就是一個管理dao層(或者叫做管理數據庫操作)的框架。類比的框架還有hibernate。

mybatis框架的特點

優點

  1. 目前爲止最爲簡單的持久層框架之一,小巧並且簡單易學。
  2. mybatis本身專注於SQL語句本身。它將SQL語句寫在xml文件之中,幾乎是徹底將程序代碼與SQL語句隔離開,耦合度相當低。因此在SQL語句的編寫上它相當的靈活。可以隨時根據業務的要求變更SQL語 句而不需要動源程序。
  3. 支持編寫動態SQL語句。mybatis提供一些獨特的標籤,可以讓程序員們很方便的進行動態SQL語句的編寫。

缺點

  1. 也因爲其的靈活性,因此SQL語句的編寫量比較大。尤其是多表聯查的時候。
  2. SQL語句因爲依賴於數據庫,因此mybatis項目的對於數據庫的可移植性比較差。

mybatis的應用場景

Mybatis是一個足夠靈活的框架。對於多變的且業務邏輯相當複雜的互聯網項目來說,mybatis是一個相當不錯的選擇

mybatis與springboot的整合

ok,接下來的纔是本篇的重點

基礎配置

首先使用maven管理jar包,需要進入關鍵依賴

<!-->mybatis關鍵依賴<-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.1</version>
</dependency>

同時本篇使用的是mysql 數據庫作爲框架測試,因此也需要引入mysql 的相關依賴

<!-->mysql關鍵依賴<-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

隨後,我們需要在application.properties中加入數據源的參數,讓springboot知道我們是如何初始化數據庫的

#驅動名稱
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#數據庫鏈接地址
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=true
#登錄用戶名
spring.datasource.username=root
#登錄密碼
spring.datasource.password=root

這四條數據是必要的,完整的配置(比如最大連接數和最小連接數之類的屬性)可以參考這個網址:
完整參數列表

關於sqlsession

springboot會自動加載spring.datasource.*相關配置,數據源就會自動注入到sqlSessionFactory中,sqlSessionFactory會自動注入到Mapper中,對,你一切都不用管了,直接拿起來使用就行了
先說一下什麼是sqlSession,說白了,可以簡單理解爲就是一個把你對數據庫的操作事務管理綁定起來的一個東西。
如果你不將對數據庫的操作放入sqlsession裏,那麼將不會參與事務,自然也就不存在事務管理這個說法。

使用方法

以上配置完之後,mybatis與springboot整合的基本配置就完成了。由於mybatis提供mapper註解兩種方式,並且這兩種方式的用法差別相當大,因此下面分開進行講解。

Mapper方法(適合涉及到多表聯查之類的業務邏輯比較複雜的項目)

這種方法屬於傳統的老方法,需要通過mapper.xml來管理SQL語句。

項目結構

整體的項目結構如下:
項目結構

額外配置

採用這種方法進行編寫,我們要注意需要另外增加幾個配置
需要在application.properties裏面添加如下代碼,以便springboot能掃描到靜態文件中的以Mapper爲結尾名的.xml文件。

#mapper掃描地址
mybatis.mapper-locations=classpath:/mapper/*Mapper.xml

在application啓動類,我們需要另外添加一個註釋,來聲明mapper藉口地址
這裏寫圖片描述

*Mapper.xml與*Mapper類的區別

這裏大家就發現了,爲什麼同一個mapper要在不同的地方聲明兩遍,這兩個mapper又有什麼不同?
我們來看這兩個文件的區別。首先是測試工程的xml文件
(注:mapper文件intellij IDEA一開始在代碼上會有非常噁心的黃綠重疊的警告符,去掉的方式參考下面這個博客:如何讓世界變得乾淨 。去掉那些提示後你會覺得世界終於清淨了)
這裏寫圖片描述

附上代碼

<?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">
<!-- namespace必須指向Dao接口 -->
<mapper namespace="com.example.mybatis_test2.dao.StudentMapper">
    <select id="selectStudentAll" resultType="map">
      SELECT * FROM Student
    </select>

    <select id="selectSomeStudent"  parameterType="com.example.mybatis_test2.model.Student" resultType="map">
      SELECT * FROM Student
      <where>
          <if test="s_id!=null and s_id!=''">
              AND s_id=#{s_id}
          </if>
          <if test="s_name!=null and s_name!=''">
              AND s_name=#{s_name}
          </if>
          <if test="s_age!=null and s_age!=''">
              AND s_age=#{s_age}
          </if>
      </where>
    </select>

    <insert id="insertStudent" parameterType="com.example.mybatis_test2.model.Student">
        INSERT INTO Student VALUES (#{s_id},#{s_name},#{s_age})
    </insert>

</mapper>

接下來是*mapper類

import com.example.mybatis_test2.model.Student;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Map;

/**
 * @author 無_言
 * mapper接口類
 * */

@Repository("/studentMapper")
public interface StudentMapper {
    //獲得數據庫中Student表的所有數據
    public List<Map> selectStudentAll();
    //根據條件查詢
    public List<Map> selectSomeStudent(Student student);
    //新增學生
    public void insertStudent(Student student);
}

經過上面兩個圖的對比,大家可以明顯看出來實際上interface裏面的方法每一個方法名就對應着mapper.xml裏面的每一個id。這樣,在Java代碼裏調用這些接口方法的時候,參數就能傳遞進mapper.xml文件裏面並執行相應的數據庫操作了。
這個interface在spring裏也可以當做一個一般的dao層方法,交給spring的IOC進行注入,並在service層進行調用。
Service層部分代碼

@Service("/studentSrv")
public class StudentSrv {

    @Resource(name = "/studentMapper")
    private StudentMapper studentMapper;
    //獲得數據庫中Student表的所有數據
    public List getStudent() throws Exception {
        return studentMapper.selectStudentAll();
    }
}

controller層部分代碼

@RestController
@RequestMapping("studentCtr")
public class StudentCtr {

    @Resource(name = "studentSrv")
    private StudentSrv studentSrv;
    //獲得數據庫中Student表的所有數據
    @RequestMapping("/studentAll")
    public List studentAll() throws Exception {
        return studentSrv.getStudent();
    }
}

程序測試結果

所有結果以json的方式傳回前臺
這裏寫圖片描述

註解方法(適合業務邏輯相對簡單,單表操作較多的項目)

前言

首先,本方法官方並不提倡使用
這裏寫圖片描述


官方原文:因爲最初設計時,MyBatis 是一個 XML 驅動的框架。配置信息是基於 XML 的,而且 映射語句也是定義在 XML 中的。而到了 MyBatis 3,有新的可用的選擇了。MyBatis 3 構建 在基於全面而且強大的 Java 配置 API 之上。這個配置 API 是基於 XML 的 MyBatis 配置的 基礎,也是新的基於註解配置的基礎。註解提供了一種簡單的方式來實現簡單映射語句,而 不會引入大量的開銷。
不幸的是,Java 註解限制了它們的表現和靈活。儘管很多時間都花調查,設計和 實驗上,最強大的 MyBatis 映射不能用註解來構建,那並不可笑。C#屬性(做示例)就沒 有這些限制,因此 MyBatis.NET 將會比 XML 有更豐富的選擇。也就是說,基於 Java 註解 的配置離不開它的特性。


說直白點,官方的意思就是,使用註解並不能體驗到mybatis的完整的強大特性和功能。若想充分使用mybatis,請使用mapper.xml的方法

項目結構

採用註解方式時,項目整體架構如下:
這裏寫圖片描述

對比xml傳統模式,在靜態資源處少了一個*mapper包,並且在配置文件application.properties處也不需要*配置mapper的路徑

使用方法

現在查看dao層的StudentMapper的代碼

import com.example.mybatis_test.sqlProvider.StudentProvider;
import com.example.mybatis_test.vo.Student;
import com.example.mybatis_test.vo.Student1;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Map;

/**
 *@author 無_言
 * 使用註解方法時的interface
 * */


@Repository("studentMapper")
public interface StudentMapper {

    //查看數據庫Student表中的所有數據
    @Select("SELECT * FROM Student")
    List<Map> getAll();

    //按照條件查詢
    @Select("SELECT * FROM Student WHERE s_id=#{s_id} and s_name=#{s_name}")
    List<Map> getSome(Student student);

    //新增
    @Insert("INSERT INTO Student VALUES(#{s_id},#{s_name},#{s_age})")
    void insert(Student student);

    //修改
    @Update("UPDATE Student SET s_name=#{s_name},s_age=#{s_age} WHERE s_id =#{s_id}")
    void update(Map map);

    //刪除
    @Delete("DELETE FROM Student WHERE s_id=#{s_id}")
    void delete(String s_id);
}

對比傳統方法,註解方法相當於是把SQL語句給寫在Java類裏面。之後的操作就和第一種方法一樣了,service層調用該接口的對應方法,然後controller層再調用service層,最後傳遞數據


在使用@Select註解時,有一個需要注意的地方,下面進行解釋。首先查看所要查的數據類型
首先是數據庫的字段名
這裏寫圖片描述

然後是Student類

public class Student {

    private Integer s_id;
    private String s_name;
    private Integer s_age;

    //省略號部分代表set和get方法
    ..........
}

最後是Student1類

public class Student1 {

    private Integer id;
    private String name;
    private Integer age;

    //省略號部分代表set和get方法
    ..........
}

很明顯,Student類和student1類不同之處在於Student類中的字段和數據庫中的字段名一一對應,而student1類中的屬性名不一致
再來看查詢方法
首先是對Student類進行查詢

dao層

//獲得所有學生,通過Student類型,Student類型的屬性字段名與數據庫中字段名一致
    @Select("SELECT * FROM Student")
    List<Student> getAllStudent();

service層

@Service("studentService")
public class StudentService {

    @Resource(name = "studentMapper")
    private StudentMapper studentMapper;

    public List<Student> showStudent1(){
        return  studentMapper.getAllStudent();
    }
}

controller層
並輸出Student類中的所有屬性

@RequestMapping("/testStudent")
public List<Student> testStudent(){
    List<Student> test = studentService.showStudent1();
    //將每個Student的屬性全部輸出,查看是否注入成功
    for (int i = 0; i <test.size() ; i++) {
        System.out.println("id"+test.get(i).getS_id());
        System.out.println("姓名"+test.get(i).getS_name());
        System.out.println("年齡"+test.get(i).getS_age());
    }
    return test;
}

查看控制檯
這裏寫圖片描述

發現數據庫的數據已經直接注入到每個對象裏了


再來看Student1類的使用方法 故意遺漏了id屬性
dao層

//獲得所有學生,通過Student1類型,Student1類型中的屬性名與數據庫中的字段名不一樣
@Select("SELECT * FROM Student")
 @Results({
         @Result(property = "name",  column = "s_name"),
         @Result(property = "age", column = "s_age")
 })
 List<Student1> getAllStudent1();

service層

public List<Student1> showStudent2(){
     return  studentMapper.getAllStudent1();
}

controller層

@RequestMapping("/testStudent1")
public List<Student1> testStudent1(){
     List<Student1> test = studentService.showStudent2();
     for (int i = 0; i <test.size() ; i++) {
         System.out.println("id"+test.get(i).getId());
         System.out.println("姓名"+test.get(i).getName());
         System.out.println("年齡"+test.get(i).getAge());

     }
     return test;
 }

控制檯輸出
這裏寫圖片描述

發現id沒有數據


由以上可以得出
@Results@Result配套使用的目的,就是爲了方便直接將數據注入進對象中,方便使用。
如果一開始創建類的時候就將屬性名與數據庫中的字段對應好,那麼該註解也就不用寫

@*Provider註解

mybatis3爲了方便我們編寫動態sql語句,提供了另外一類註解,分別是@SelectProvider
@InsertProvider @UpdateProvider @DeleteProvider 我們以@SelectProvider爲例講解

在mapper中的書寫方法如下

//通過mybatis3提供的provider來構建動態sql語句
    @SelectProvider(type = StudentProvider.class, method = "findOneStudentSql")
    Student findOneStudent(Map<String,Object> map);

StudentProvider類所擺放的位置如下
這裏寫圖片描述

其具體內容爲:

import org.apache.ibatis.jdbc.SQL;

import java.util.Map;

/**
 * @author 無_言
 * 註解使用動態sql的方法
 * */

public class StudentProvider {

    public String findOneStudentSql(Map<String,Object> map){
        return new SQL(){
            {
                SELECT("*");
                FROM("Student");
                WHERE("s_id="+map.get("id"));
            }
        }.toString();
    }

}

可見類名對應@SelectProvider中的type屬性,所要執行的方法名對應@SelectProvider中的method屬性

service層

public Student showOneStudent(Map<String,Object> map){
  return studentMapper.findOneStudent( map);
}

controller層

@RequestMapping("/findOneStudent")
public List<Student> findOneStudent(HttpServletRequest request){
    List list=new ArrayList();//創建一個list來存放返回值
    Student student = new Student();
    student.setS_id(Integer.parseInt(request.getParameter("id")));//從地址欄獲取id值
    Map<String,Object> map=new HashMap<String, Object>();
    map.put("id",student.getS_id());//將id值傳入map,並將map作爲參數傳入StudentProvider
    list.add(studentService.showOneStudent(map));
    return list;
}

程序測試(針對provider註解)

網頁效果圖
這裏寫圖片描述

這裏寫圖片描述

關於mybatis的事務管理

關於事務,springboot-mybatis提供了兩個註解來支持事務管理
分別是@EnableTransactionManagement@Transactional
@EnableTransactionManagement註解必須放在啓動類
這裏寫圖片描述

@Transactional註解有兩種位置可以擺放。(注:該註解spring官方不建議放在接口上,會出一些意想不到的錯誤)
第一種是放在要添加事務的類上,例子如下

這裏寫圖片描述

加上後,該類下的所有public方法都會加上事務


另外一種方法是添加在要加事務的方法上
這裏寫圖片描述

程序測試

這裏寫圖片描述

故意新增兩個id值爲一樣數據,如果事務管理沒有成功,那麼數據庫的效果是只增加了一條數據
而事務管理添加成功之後,數據庫的結果應該是一條數據也沒有插進去

執行結果:
控制檯:
這裏寫圖片描述
控制檯報錯,控制檯報錯,不能有相同id的數據

未加事務管理時的數據庫,執行了第一條插入
這裏寫圖片描述

加入事務管理後,數據庫數據庫沒有新增任何數據,還是保留着上次操作的最後一條記錄
這裏寫圖片描述
事務測試成功

參考資料

十分感謝以下博客的編寫者,給我提供了很多資料和信息
https://my.oschina.net/liuyuantao/blog/791884
http://blog.csdn.net/mickjoust/article/details/51646658
http://www.bubuko.com/infodetail-1808247.html
http://blog.csdn.net/zhugeyangyang1994/article/details/52045924
http://blog.csdn.net/gongsunjinqian/article/details/52710557
http://blog.csdn.net/lxhjh/article/details/51764604
http://blog.csdn.net/catoop/article/details/50684676
http://blog.csdn.net/sinat_36203615/article/details/53759935

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章