日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

mybatis看这一篇就够了,简单全面一发入魂

發(fā)布時間:2023/12/3 综合教程 32 生活家
生活随笔 收集整理的這篇文章主要介紹了 mybatis看这一篇就够了,简单全面一发入魂 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • Mybatis
    • 概述
    • 快速入門
      • 原生開發(fā)示例
      • 基于Mapper代理的示例
      • 基于注解的示例
    • 應(yīng)用場景
      • 主鍵返回
      • 批量查詢
      • 動態(tài)SQL
      • 緩存
      • 關(guān)聯(lián)查詢
      • 延遲加載
      • 逆向工程
      • PageHelper分頁插件
      • Mybatis Plus

Mybatis

概述

  1. mybatis是什么?有什么特點?

    它是一款半自動的ORM持久層框架,具有較高的SQL靈活性,支持高級映射(一對一,一對多),動態(tài)SQL,延遲加載和緩存等特性,但它的數(shù)據(jù)庫無關(guān)性較低

    • 什么是ORM?

      Object Relation Mapping,對象關(guān)系映射。對象指的是Java對象,關(guān)系指的是數(shù)據(jù)庫中的關(guān)系模型,對象關(guān)系映射,指的就是在Java對象和數(shù)據(jù)庫的關(guān)系模型之間建立一種對應(yīng)關(guān)系,比如用一個Java的Student類,去對應(yīng)數(shù)據(jù)庫中的一張student表,類中的屬性和表中的列一一對應(yīng)。Student類就對應(yīng)student表,一個Student對象就對應(yīng)student表中的一行數(shù)據(jù)

    • 為什么mybatis是半自動的ORM框架?

      用mybatis進行開發(fā),需要手動編寫SQL語句。而全自動的ORM框架,如hibernate,則不需要編寫SQL語句。用hibernate開發(fā),只需要定義好ORM映射關(guān)系,就可以直接進行CRUD操作了。由于mybatis需要手寫SQL語句,所以它有較高的靈活性,可以根據(jù)需要,自由地對SQL進行定制,也因為要手寫SQL,當(dāng)要切換數(shù)據(jù)庫時,SQL語句可能就要重寫,因為不同的數(shù)據(jù)庫有不同的方言(Dialect),所以mybatis的數(shù)據(jù)庫無關(guān)性低。雖然mybatis需要手寫SQL,但相比JDBC,它提供了輸入映射和輸出映射,可以很方便地進行SQL參數(shù)設(shè)置,以及結(jié)果集封裝。并且還提供了關(guān)聯(lián)查詢動態(tài)SQL等功能,極大地提升了開發(fā)的效率。并且它的學(xué)習(xí)成本也比hibernate低很多

快速入門

只需要通過如下幾個步驟,即可用mybatis快速進行持久層的開發(fā)

  1. 編寫全局配置文件
  2. 編寫mapper映射文件
  3. 加載全局配置文件,生成SqlSessionFactory
  4. 創(chuàng)建SqlSession,調(diào)用mapper映射文件中的SQL語句來執(zhí)行CRUD操作

原生開發(fā)示例

  1. 在本地虛擬機mysql上創(chuàng)建一個庫yogurt,并在里面創(chuàng)建一張student表

  2. 打開IDEA,創(chuàng)建一個maven項目

  3. 導(dǎo)入依賴的jar包

    	<dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.10</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.6</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version><scope>provided</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.10</version><scope>test</scope></dependency></dependencies>
    
  4. 創(chuàng)建一個po類

    package com.yogurt.po;import lombok.*;@Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class Student {private Integer id;private String name;private Integer score;private Integer age;private Integer gender;}
  5. 編寫mapper映射文件(編寫SQL)

    <!-- StudentMapper.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="test"><select id="findAll" resultType="com.yogurt.po.Student">SELECT * FROM student;</select><insert id="insert" parameterType="com.yogurt.po.Student">INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});</insert><delete id="delete" parameterType="int">DELETE FROM student WHERE id = #{id};</delete>
    </mapper>
    
  6. 編寫數(shù)據(jù)源properties文件

    db.url=jdbc:mysql://192.168.183.129:3306/yogurt?characterEncoding=utf8
    db.user=root
    db.password=root
    db.driver=com.mysql.jdbc.Driver
    
  7. 編寫全局配置文件(主要是配置數(shù)據(jù)源信息)

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration><!-- 配置文件信息 --><properties resource="properties/db.properties"></properties><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><!-- 從配置文件中加載屬性 --><property name="driver" value="${db.driver}"/><property name="url" value="${db.url}"/><property name="username" value="${db.user}"/><property name="password" value="${db.password}"/></dataSource></environment></environments><mappers><!-- 加載前面編寫的SQL語句的文件 --><mapper resource="StudentMapper.xml"/></mappers></configuration>
    
  8. 編寫dao類

    package com.yogurt.dao;import com.yogurt.po.Student;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;
    import java.io.InputStream;
    import java.util.List;public class StudentDao {private SqlSessionFactory sqlSessionFactory;public StudentDao(String configPath) throws IOException {InputStream inputStream = Resources.getResourceAsStream(configPath);sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);}public List<Student> findAll() {SqlSession sqlSession = sqlSessionFactory.openSession();List<Student> studentList = sqlSession.selectList("findAll");sqlSession.close();return studentList;}public int addStudent(Student student) {SqlSession sqlSession = sqlSessionFactory.openSession();int rowsAffected = sqlSession.insert("insert", student);sqlSession.commit();sqlSession.close();return rowsAffected;}public int deleteStudent(int id) {SqlSession sqlSession = sqlSessionFactory.openSession();int rowsAffected = sqlSession.delete("delete",id);sqlSession.commit();sqlSession.close();return rowsAffected;}
    }
  9. 測試

    public class SimpleTest {private StudentDao studentDao;@Beforepublic void init() throws IOException {studentDao = new StudentDao("mybatis-config.xml");}@Testpublic void insertTest() {Student student = new Student();student.setName("yogurt");student.setAge(24);student.setGender(1);student.setScore(100);studentDao.addStudent(student);}@Testpublic void findAllTest() {List<Student> all = studentDao.findAll();all.forEach(System.out::println);}
    }
    

總結(jié)

  1. 編寫mapper.xml,書寫SQL,并定義好SQL的輸入?yún)?shù),和輸出參數(shù)
  2. 編寫全局配置文件,配置數(shù)據(jù)源,以及要加載的mapper.xml文件
  3. 通過全局配置文件,創(chuàng)建SqlSessionFactory
  4. 每次進行CRUD時,通過SqlSessionFactory創(chuàng)建一個SqlSession
  5. 調(diào)用SqlSession上的selectOneselectListinsertdeleteupdate等方法,傳入mapper.xml中SQL標(biāo)簽的id,以及輸入?yún)?shù)

注意要點

  1. 全局配置文件中,各個標(biāo)簽要按照如下順序進行配置,因為mybatis加載配置文件的源碼中是按照這個順序進行解析的

    <configuration><!-- 配置順序如下properties  settingstypeAliasestypeHandlersobjectFactorypluginsenvironmentsenvironmenttransactionManagerdataSourcemappers-->
    </configuration>
    

    各個子標(biāo)簽說明如下

    • <properties>

      一般將數(shù)據(jù)源的信息單獨放在一個properties文件中,然后用這個標(biāo)簽引入,在下面environment標(biāo)簽中,就可以用${}占位符快速獲取數(shù)據(jù)源的信息

    • <settings>

      用來開啟或關(guān)閉mybatis的一些特性,比如可以用<setting name="lazyLoadingEnabled" value="true"/>來開啟延遲加載,可以用<settings name="cacheEnabled" value="true"/>來開啟二級緩存

    • <typeAliases>

      在mapper.xml中需要使用parameterTyperesultType屬性來配置SQL語句的輸入?yún)?shù)類型和輸出參數(shù)類型,類必須要寫上全限定名,比如一個SQL的返回值映射為Student類,則resultType屬性要寫com.yogurt.po.Student,這太長了,所以可以用別名來簡化書寫,比如

      <typeAliases><typeAlias type="com.yogurt.po.Student" alias="student"/>
      </typeAliases>
      

      之后就可以在resultType上直接寫student,mybatis會根據(jù)別名配置自動找到對應(yīng)的類。

      當(dāng)然,如果想要一次性給某個包下的所有類設(shè)置別名,可以用如下的方式

      <typeAliases><package name="com.yogurt.po"/>
      </typeAliases>
      

      如此,指定包下的所有類,都會以簡單類名的小寫形式,作為它的別名

      另外,對于基本的Java類型 -> 8大基本類型以及包裝類,以及String類型,mybatis提供了默認(rèn)的別名,別名為其簡單類名的小寫,比如原本需要寫java.lang.String,其實可以簡寫為string

    • <typeHandlers>

      用于處理Java類型和Jdbc類型之間的轉(zhuǎn)換,mybatis有許多內(nèi)置的TypeHandler,比如StringTypeHandler,會處理Java類型String和Jdbc類型CHAR和VARCHAR。這個標(biāo)簽用的不多

    • <objectFactory>

      mybatis會根據(jù)resultTyperesultMap的屬性來將查詢得到的結(jié)果封裝成對應(yīng)的Java類,它有一個默認(rèn)的DefaultObjectFactory,用于創(chuàng)建對象實例,這個標(biāo)簽用的也不多

    • <plugins>

      可以用來配置mybatis的插件,比如在開發(fā)中經(jīng)常需要對查詢結(jié)果進行分頁,就需要用到pageHelper分頁插件,這些插件就是通過這個標(biāo)簽進行配置的。在mybatis底層,運用了責(zé)任鏈模式+動態(tài)代理去實現(xiàn)插件的功能

      <!-- PageHelper 分頁插件 -->
      <plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"><property name="helperDialect" value="mysql"/></plugin>
      </plugins>
      
    • <environments>

      用來配置數(shù)據(jù)源

    • <mappers>

      用來配置mapper.xml映射文件,這些xml文件里都是SQL語句

  2. mapper.xml的SQL語句中的占位符${}#{}

    一般會采用#{}#{}在mybatis中,最后會被解析為?,其實就是Jdbc的PreparedStatement中的?占位符,它有預(yù)編譯的過程,會對輸入?yún)?shù)進行類型解析(如果入?yún)⑹荢tring類型,設(shè)置參數(shù)時會自動加上引號),可以防止SQL注入,如果parameterType屬性指定的入?yún)㈩愋褪呛唵晤愋偷脑?簡單類型指的是8種java原始類型再加一個String),#{}中的變量名可以任意,如果入?yún)㈩愋褪莗ojo,比如是Student類

    public class Student{private String name;private Integer age;//setter/getter
    }
    

    那么#{name}表示取入?yún)ο骃tudent中的name屬性,#{age}表示取age屬性,這個過程是通過反射來做的,這不同于${}${}取對象的屬性使用的是OGNL(Object Graph Navigation Language)表達式

    ${},一般會用在模糊查詢的情景,比如SELECT * FROM student WHERE name like '%${name}%';

    它的處理階段在#{}之前,它不會做參數(shù)類型解析,而僅僅是做了字符串的拼接,若入?yún)⒌腟tudent對象的name屬性為zhangsan,則上面那條SQL最終被解析為SELECT * FROM student WHERE name like '%zhangsan%';

    而如果此時用的是SELECT * FROM student WHERE name like '%#{name}%'; 這條SQL最終就會變成

    SELECT * FROM student WHERE name like '%'zhangsan'%'; 所以模糊查詢只能用${},雖然普通的入?yún)⒁部梢杂?code>${},但由于${}不會做類型解析,就存在SQL注入的風(fēng)險,比如

    SELECT * FROM user WHERE name = '${name}' AND password = '${password}'

    我可以讓一個user對象的password屬性為'OR '1' = '1,最終的SQL就變成了

    SELECT * FROM user WHERE name = 'yogurt' AND password = ''OR '1' = '1',因為OR '1' = '1'恒成立,這樣攻擊者在不需要知道用戶名和密碼的情況下,也能夠完成登錄驗證

    另外,對于pojo的入?yún)?#xff0c;${}中獲取對象屬性的語法和#{}幾乎一樣,但${}在mybatis底層是通過OGNL表達式語言進行處理的,這跟#{}的反射處理有所不同

    對于簡單類型(8種java原始類型再加一個String)的入?yún)?#xff0c;${}中參數(shù)的名字必須是value,例子如下

    <select id="fuzzyCount" parameterType="string" resultType="int">SELECT count(1) FROM `user` WHERE name like '%${value}%'
    </select>
    

    為什么簡單類型的變量名必須為value呢?因為mybatis源碼中寫死的value,哈哈

上面其實是比較原始的開發(fā)方式,我們需要編寫dao類,針對mapper.xml中的每個SQL標(biāo)簽,做一次封裝,SQL標(biāo)簽的id要以字符串的形式傳遞給SqlSession的相關(guān)方法,容易出錯,非常不方便;為了簡化開發(fā),mybatis提供了mapper接口代理的開發(fā)方式,不需要再編寫dao類,只需要編寫一個mapper接口,一個mapper的接口和一個mapper.xml相對應(yīng),只需要調(diào)用SqlSession對象上的getMapper(),傳入mapper接口的class信息,即可獲得一個mapper代理對象,直接調(diào)用mapper接口中的方法,即相當(dāng)于調(diào)用mapper.xml中的各個SQL標(biāo)簽,此時就不需要指定SQL標(biāo)簽的id字符串了,mapper接口中的一個方法,就對應(yīng)了mapper.xml中的一個SQL標(biāo)簽

基于Mapper代理的示例

全局配置文件和mapper.xml文件是最基本的配置,仍然需要。不過,這次我們不編寫dao類,我們直接創(chuàng)建一個mapper接口

package com.yogurt.mapper;import com.yogurt.po.Student;import java.util.List;public interface StudentMapper {List<Student> findAll();int insert(Student student);int delete(Integer id);List<Student> findByName(String value);
}

而我們的mapper.xml文件如下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.yogurt.mapper.StudentMapper"><select id="findAll" resultType="com.yogurt.po.Student">SELECT * FROM student;</select><insert id="insert" parameterType="com.yogurt.po.Student">INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});</insert><delete id="delete" parameterType="int">DELETE FROM student WHERE id = #{id};</delete><select id="findByName" parameterType="string" resultType="student">SELECT * FROM student WHERE name like '%${value}%';</select>
</mapper>

mapper接口和mapper.xml之間需要遵循一定規(guī)則,才能成功的讓mybatis將mapper接口和mapper.xml綁定起來

  1. mapper接口的全限定名,要和mapper.xml的namespace屬性一致
  2. mapper接口中的方法名要和mapper.xml中的SQL標(biāo)簽的id一致
  3. mapper接口中的方法入?yún)㈩愋?#xff0c;要和mapper.xml中SQL語句的入?yún)㈩愋鸵恢?/li>
  4. mapper接口中的方法出參類型,要和mapper.xml中SQL語句的返回值類型一致

測試代碼如下

public class MapperProxyTest {private SqlSessionFactory sqlSessionFactory;@Beforepublic void init() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);}@Testpublic void test() {SqlSession sqlSession = sqlSessionFactory.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List<Student> studentList = mapper.findAll();studentList.forEach(System.out::println);}
}

結(jié)果如下

這個mapper接口,mybatis會自動找到對應(yīng)的mapper.xml,然后對mapper接口使用動態(tài)代理的方式生成一個代理類

基于注解的示例

如果實在看xml配置文件不順眼,則可以考慮使用注解的開發(fā)方式,不過注解的開發(fā)方式,會將SQL語句寫到代碼文件中,后續(xù)的維護性和擴展性不是很好(如果想修改SQL語句,就得改代碼,得重新打包部署,而如果用xml方式,則只需要修改xml,用新的xml取替換舊的xml即可)

使用注解的開發(fā)方式,也還是得有一個全局配置的xml文件,不過mapper.xml就可以省掉了,具體操作只用2步,如下

  1. 創(chuàng)建一個Mapper接口

    package com.yogurt.mapper;
    import com.yogurt.po.Student;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Select;
    import java.util.List;public interface PureStudentMapper {@Select("SELECT * FROM student")List<Student> findAll();@Insert("INSERT INTO student (name,age,score,gender) VALUES (#{name},#{age},#{score},#{gender})")int insert(Student student);
    }
    
  2. 在全局配置文件中修改<mappers>標(biāo)簽,直接指定加載這個類

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration><properties resource="properties/db.properties"></properties><typeAliases><package name="com.yogurt.po"/></typeAliases><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${db.driver}"/><property name="url" value="${db.url}"/><property name="username" value="${db.user}"/><property name="password" value="${db.password}"/></dataSource></environment></environments><mappers><mapper class="com.yogurt.mapper.PureStudentMapper"/></mappers></configuration>
    

測試代碼如下

public class PureMapperTest {private SqlSessionFactory sqlSessionFactory;@Beforepublic void init() throws IOException {InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);}@Testpublic void test() {SqlSession sqlSession = sqlSessionFactory.openSession();PureStudentMapper mapper = sqlSession.getMapper(PureStudentMapper.class);mapper.insert(new Student(10,"Tomcat",120,60,0));sqlSession.commit();List<Student> studentList = mapper.findAll();studentList.forEach(System.out::println);}
}

結(jié)果如下


注:當(dāng)使用注解開發(fā)時,若需要傳入多個參數(shù),可以結(jié)合@Param注解,示例如下

package org.mybatis.demo.mapper;import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.mybatis.demo.po.Student;import java.util.List;public interface PureStudentMapper {@Select("SELECT * FROM student WHERE name like '%${name}%' AND major like '%${major}%'")List<Student> find(@Param("name") String name, @Param("major") String major);
}

@Param標(biāo)簽會被mybatis處理并封裝成一個Map對象,比如上面的示例中,實際傳入的參數(shù)是一個Map對象,@Param標(biāo)簽幫忙向Map中設(shè)置了值,即它做了

Map<String,Object> map = new HashMap<>();
map.put("name", name);
map.put("major",major);

將方法形參中的namemajor放到了map對象中,所以在@Select標(biāo)簽中可以用${name}${major}取出map對象中的值。
--------------------(我是分割線)

上面我們見到了在全局配置文件中,兩種配置mapper的方式,分別是

<!-- 在mapper接口中使用注解 -->
<mappers><mapper class="com.yogurt.mapper.PureStudentMapper"/>
</mappers><!-- 普通加載xml -->
<mappers><mapper resource="StudentMapper.xml"/>
</mappers>

而在實際工作中,一般我們會將一張表的SQL操作封裝在一個mapper.xml中,可能有許多張表需要操作,那么我們是不是要在<mappers>標(biāo)簽下寫多個<mapper>標(biāo)簽?zāi)?#xff1f;其實不用,還有第三種加載mapper的方法,使用<package>標(biāo)簽

<mappers><package name="com.yogurt.mapper"/>
</mappers>

這樣就會自動加載com.yogurt.mapper包下的所有mapper,這種方式需要將mapper接口文件和mapper.xml文件都放在com.yogurt.mapper包下,且接口文件和xml文件的文件名要一致。注意,在IDEA的maven開發(fā)環(huán)境下,maven中還需配置<resources>標(biāo)簽,否則maven打包不會將java源碼目錄下的xml文件打包進去,見下文

三種加載mapper的方式總結(jié)

  • <mapper resource="" />

    加載普通的xml文件,傳入xml的相對路徑(相對于類路徑)

  • <mapper class="" />

    使用mapper接口的全限定名來加載,若mapper接口采用注解方式,則不需要xml;若mapper接口沒有采用注解方式,則mapper接口和xml文件的名稱要相同,且在同一個目錄

  • <package name="" />

    掃描指定包下的所有mapper,若mapper接口采用注解方式,則不需要xml;若mapper接口沒有采用注解方式,則mapper接口和xml文件的名稱要相同,且在同一目錄

注意:用后兩種方式加載mapper接口和mapper.xml映射文件時,可能會報錯

仔細檢查了一下,mapper接口文件和xml映射文件確實放在了同一個目錄下,而且文件名一致,xml映射文件的namespace也和mapper接口的全限定名對的上。為什么會這樣呢?

其實是因為,對于src/main/java 源碼目錄下的文件,maven打包時只會將該目錄下的java文件打包,而其他類型的文件都不會被打包進去,去工程目錄的target目錄下看看maven構(gòu)建后生成的文件

我們需要在pom.xml中的<build> 標(biāo)簽下 添加<resources> 標(biāo)簽,指定打包時要將xml文件打包進去

<build><resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource></resources>
</build>

此時再用maven進行打包,看到對應(yīng)目錄下有了xml映射文件(特別注意,這里配置了pom.xml下的resource標(biāo)簽后,可能會引發(fā)一些問題,例如原本src/main/resources資源目錄下的文件沒有被打包進來,參考我的這篇文章maven打包時的資源文件問題)

此時再運行單元測試,就能正常得到結(jié)果了

應(yīng)用場景

主鍵返回

通常我們會將數(shù)據(jù)庫表的主鍵id設(shè)為自增。在插入一條記錄時,我們不設(shè)置其主鍵id,而讓數(shù)據(jù)庫自動生成該條記錄的主鍵id,那么在插入一條記錄后,如何得到數(shù)據(jù)庫自動生成的這條記錄的主鍵id呢?有兩種方式

  1. 使用useGeneratedKeyskeyProperty屬性

    <insert id="insert" parameterType="com.yogurt.po.Student" useGeneratedKeys="true" keyProperty="id">INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});</insert>
    
  2. 使用<selectKey>子標(biāo)簽

    <insert id="insert" parameterType="com.yogurt.po.Student">INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});<selectKey keyProperty="id" order="AFTER" resultType="int" >SELECT LAST_INSERT_ID();</selectKey></insert>
    

    如果使用的是mysql這樣的支持自增主鍵的數(shù)據(jù)庫,可以簡單的使用第一種方式;對于不支持自增主鍵的數(shù)據(jù)庫,如oracle,則沒有主鍵返回這一概念,而需要在插入之前先生成一個主鍵。此時可以用<selectKey>標(biāo)簽,設(shè)置其order屬性為BEFORE,并在標(biāo)簽體內(nèi)寫上生成主鍵的SQL語句,這樣在插入之前,會先處理<selectKey>,生成主鍵,再執(zhí)行真正的插入操作。

    <selectKey>標(biāo)簽其實就是一條SQL,這條SQL的執(zhí)行,可以放在主SQL執(zhí)行之前或之后,并且會將其執(zhí)行得到的結(jié)果封裝到入?yún)⒌腏ava對象的指定屬性上。注意<selectKey>子標(biāo)簽只能用在<insert><update>標(biāo)簽中。上面的LAST_INSERT_ID()實際上是MySQL提供的一個函數(shù),可以用來獲取最近插入或更新的記錄的主鍵id。

測試代碼如下

public class MapperProxyTest {private SqlSessionFactory sqlSessionFactory;@Beforepublic void init() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);}@Testpublic void test() {SqlSession sqlSession = sqlSessionFactory.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = new Student(-1, "Podman", 130, 15, 0);mapper.insert(student);sqlSession.commit();System.out.println(student.getId());}
}

結(jié)果如下

批量查詢

主要是動態(tài)SQL標(biāo)簽的使用,注意如果parameterTypeList的話,則在標(biāo)簽體內(nèi)引用這個List,只能用變量名list,如果parameterType是數(shù)組,則只能用變量名array

<select id="batchFind" resultType="student" parameterType="java.util.List">SELECT * FROM student<where><if test="list != null and list.size() > 0">AND id in<foreach collection="list" item="id" open="(" separator="," close=")">#{id}</foreach></if></where>
</select>
	@Testpublic void testBatchQuery() {SqlSession sqlSession = sqlSessionFactory.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List<Student> students = mapper.batchFind(Arrays.asList(1, 2, 3, 7, 9));students.forEach(System.out::println);}

結(jié)果

動態(tài)SQL

可以根據(jù)具體的參數(shù)條件,來對SQL語句進行動態(tài)拼接。

比如在以前的開發(fā)中,由于不確定查詢參數(shù)是否存在,許多人會使用類似于where 1 = 1 來作為前綴,然后后面用AND 拼接要查詢的參數(shù),這樣,就算要查詢的參數(shù)為空,也能夠正確執(zhí)行查詢,如果不加1 = 1,則如果查詢參數(shù)為空,SQL語句就會變成SELECT * FROM student where ,SQL不合法。

mybatis里的動態(tài)標(biāo)簽主要有

  • if

    <!-- 示例 -->
    <select id="find" resultType="student" parameterType="student">SELECT * FROM student WHERE age >= 18<if test="name != null and name != ''">AND name like '%${name}%'</if>
    </select>
    

    當(dāng)滿足test條件時,才會將<if>標(biāo)簽內(nèi)的SQL語句拼接上去

  • choose

    <!-- choose 和 when , otherwise 是配套標(biāo)簽 
    類似于java中的switch,只會選中滿足條件的一個
    -->
    <select id="findActiveBlogLike"resultType="Blog">SELECT * FROM BLOG WHERE state = ‘ACTIVE’<choose><when test="title != null">AND title like #{title}</when><when test="author != null and author.name != null">AND author_name like #{author.name}</when><otherwise>AND featured = 1</otherwise></choose>
    </select>
    
  • trim

    • where

      <where>標(biāo)簽只會在至少有一個子元素返回了SQL語句時,才會向SQL語句中添加WHERE,并且如果WHERE之后是以AND或OR開頭,會自動將其刪掉

      <select id="findActiveBlogLike"resultType="Blog">SELECT * FROM BLOG<where><if test="state != null">state = #{state}</if><if test="title != null">AND title like #{title}</if><if test="author != null and author.name != null">AND author_name like #{author.name}</if></where>
      </select>
      

      <where>標(biāo)簽可以用<trim>標(biāo)簽代替

      <trim prefix="WHERE" prefixOverrides="AND | OR">...
      </trim>
      
    • set

      在至少有一個子元素返回了SQL語句時,才會向SQL語句中添加SET,并且如果SET之后是以,開頭的話,會自動將其刪掉

      <set>標(biāo)簽相當(dāng)于如下的<trim>標(biāo)簽

      <trim prefix="SET" prefixOverrides=",">...
      </trim>
      

    可以通過<trim>標(biāo)簽更加靈活地對SQL進行定制

    實際上在mybatis源碼,也能看到trim與set,where標(biāo)簽的父子關(guān)系

  • foreach

    用來做迭代拼接的,通常會與SQL語句中的IN查詢條件結(jié)合使用,注意,到parameterType為List(鏈表)或者Array(數(shù)組),后面在引用時,參數(shù)名必須為list或者array。如在foreach標(biāo)簽中,collection屬性則為需要迭代的集合,由于入?yún)⑹莻€List,所以參數(shù)名必須為list

    <select id="batchFind" resultType="student" parameterType="list">SELECT * FROM student WHERE id in<foreach collection="list" item="item" open="(" separator="," close=")">#{item}</foreach>
    </select>
    
  • sql

    可將重復(fù)的SQL片段提取出來,然后在需要的地方,使用<include>標(biāo)簽進行引用

    <select id="findUser" parameterType="user" resultType="user">SELECT * FROM user<include refid="whereClause"/>
    </select><sql id="whereClause"><where><if test="user != null">AND username like '%${user.name}%'</if></where>
    </sql>
    
  • bind

    mybatis的動態(tài)SQL都是用OGNL表達式進行解析的,如果需要創(chuàng)建OGNL表達式以外的變量,可以用bind標(biāo)簽

    <select id="selectBlogsLike" resultType="Blog"><bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />SELECT * FROM BLOGWHERE title LIKE #{pattern}
    </select>
    

緩存

  • 一級緩存

    默認(rèn)開啟,同一個SqlSesion級別共享的緩存,在一個SqlSession的生命周期內(nèi),執(zhí)行2次相同的SQL查詢,則第二次SQL查詢會直接取緩存的數(shù)據(jù),而不走數(shù)據(jù)庫,當(dāng)然,若第一次和第二次相同的SQL查詢之間,執(zhí)行了DML(INSERT/UPDATE/DELETE),則一級緩存會被清空,第二次查詢相同SQL仍然會走數(shù)據(jù)庫

    一級緩存在下面情況會被清除

    • 在同一個SqlSession下執(zhí)行增刪改操作時(不必提交),會清除一級緩存
    • SqlSession提交或關(guān)閉時(關(guān)閉時會自動提交),會清除一級緩存
    • 對mapper.xml中的某個CRUD標(biāo)簽,設(shè)置屬性flushCache=true,這樣會導(dǎo)致該MappedStatement的一級緩存,二級緩存都失效(一個CRUD標(biāo)簽在mybatis中會被封裝成一個MappedStatement)
    • 在全局配置文件中設(shè)置 <setting name="localCacheScope" value="STATEMENT"/>,這樣會使一級緩存失效,二級緩存不受影響
  • 二級緩存

    默認(rèn)關(guān)閉,可通過全局配置文件中的<settings name="cacheEnabled" value="true"/>開啟二級緩存總開關(guān),然后在某個具體的mapper.xml中增加<cache />,即開啟了該mapper.xml的二級緩存。二級緩存是mapper級別的緩存,粒度比一級緩存大,多個SqlSession可以共享同一個mapper的二級緩存。注意開啟二級緩存后,SqlSession需要提交,查詢的數(shù)據(jù)才會被刷新到二級緩存當(dāng)中

緩存的詳細分析可以參考我之前的文章 => 極簡mybatis緩存

關(guān)聯(lián)查詢

使用<resultMap> 標(biāo)簽以及<association><collection> 子標(biāo)簽,進行關(guān)聯(lián)查詢,比較簡單,不多說

延遲加載

延遲加載是結(jié)合關(guān)聯(lián)查詢進行應(yīng)用的。也就是說,只在<association><collection> 標(biāo)簽上起作用

對于關(guān)聯(lián)查詢,若不采用延遲加載策略,而是一次性將關(guān)聯(lián)的從信息都查詢出來,則在主信息比較多的情況下,會產(chǎn)生N+1問題,導(dǎo)致性能降低。比如用戶信息和訂單信息是一對多的關(guān)系,在查詢用戶信息時,設(shè)置了關(guān)聯(lián)查詢訂單信息,如不采用延遲加載策略,假設(shè)共有100個用戶,則我們查這100個用戶的基本信息只需要一次SQL查詢

select * from user;

若開啟了關(guān)聯(lián)查詢,且不是延遲加載,則對于這100個用戶,會發(fā)出100條SQL去查用戶對應(yīng)的訂單信息,這樣會造成不必要的性能開銷(其實我認(rèn)為稱之為1+N問題更為合適)

select * from orders where u_id = 1;
select * from orders where u_id = 2;
....
select * from orders where u_id = 100;

當(dāng)我們可能只關(guān)心id=3的用戶的訂單信息,則很多的關(guān)聯(lián)信息是無用的,于是,采用延遲加載策略,可以按需加載從信息,在需要某個主信息對應(yīng)的從信息時,再發(fā)送SQL去執(zhí)行查詢,而不是一次性全部查出來,這樣能很好的提升性能。

另外,針對N+1問題,除了采用延遲加載的策略按需進行關(guān)聯(lián)查詢。如果在某些場景下,確實需要查詢所有主信息關(guān)聯(lián)的從信息。在上面的例子中,就是如果確實需要把這100個用戶關(guān)聯(lián)的訂單信息全部查詢出來,那怎么辦呢?這里提供2個解決思路。

1是采用連接查詢,只使用1條SQL即可,如下

select * from user as u left join orders as o on u.id = o.u_id;

但使用連接查詢查出來的結(jié)果是兩表的笛卡爾積,還需要自行進行數(shù)據(jù)的分組處理

2是使用兩個步驟來完成,先執(zhí)行一條SQL,查出全部的用戶信息,并把用戶的id放在一個集合中,然后第二條SQL采用IN關(guān)鍵字查詢即可。這種方式也可以簡化為子查詢,如下

select * from orders where u_id in (select id from user);

現(xiàn)在說回來,mybatis的延遲加載默認(rèn)是關(guān)閉的,可以通過全局配置文件中的<setting name="lazyLoadingEnabled" value="true"/>來開啟,開啟后,所有的SELECT查詢,若有關(guān)聯(lián)對象,都會采用延遲加載的策略。當(dāng)然,也可以對指定的某個CRUD標(biāo)簽單獨禁用延遲加載策略,通過設(shè)置SELECT標(biāo)簽中的fetchType=eager,則可以關(guān)閉該標(biāo)簽的延遲加載。

(還有一個侵入式延遲加載的概念,在配置文件中通過<setting name="aggressiveLazyLoading" value="true">來開啟,大概是說,訪問主對象中的主信息時,就會觸發(fā)延遲加載,將從信息查詢上來,這其實并不是真正意義的延遲加載,真正意義上的延遲加載應(yīng)該是訪問主對象中的從信息時,才觸發(fā)延遲加載,去加載從信息,侵入式延遲加載默認(rèn)是關(guān)閉的,一般情況下可以不用管他)

注意,延遲加載在關(guān)聯(lián)查詢的場景下才有意義。需要配合<resultMap>標(biāo)簽下的<association><collecction> 標(biāo)簽使用

<!-- StudentMapper.xml -->
<resultMap id="studentExt" type="com.yogurt.po.StudentExt"><result property="id" column="id"/><result property="name" column="name"/><result property="score" column="score"/><result property="age" column="age"/><result property="gender" column="gender"/><!-- 當(dāng)延遲加載總開關(guān)開啟時,resultMap下的association和collection標(biāo)簽中,若通過select屬性指定嵌套查詢的SQL,則其fetchType默認(rèn)是lazy的,當(dāng)在延遲加載總開關(guān)開啟時,需要對個別的關(guān)聯(lián)查詢禁用延遲加載時,才有必要配置fetchType = eager --><!--column用于指定用于關(guān)聯(lián)查詢的列property用于指定要封裝到StudentExt中的哪個屬性javaType用于指定關(guān)聯(lián)查詢得到的對象select用于指定關(guān)聯(lián)查詢時,調(diào)用的是哪一個DQL--><association property="clazz" javaType="com.yogurt.po.Clazz" column="class_id"select="com.yogurt.mapper.ClassMapper.findById" fetchType="lazy"/></resultMap><select id="findLazy" parameterType="string" resultMap="studentExt">SELECT * FROM student WHERE name like '%${value}%';</select>
<!-- com.yogurt.mapper.ClassMapper -->
<select id="findById" parameterType="int" resultType="com.yogurt.po.Clazz">SELECT * FROM class WHERE id = #{id}
</select>
/** 用于封裝關(guān)聯(lián)查詢的對象 **/
public class StudentExt{private Integer id;private String name;private Integer score;private Integer age;private Integer gender;/** 關(guān)聯(lián)對象 **/private Clazz clazz;//getter/setter
}

逆向工程

mybatis官方提供了mapper自動生成工具mybatis-generator-core來針對單表,生成PO類,以及Mapper接口和mapper.xml映射文件。針對單表,可以不需要再手動編寫xml配置文件和mapper接口文件了,非常方便。美中不足的是它不支持生成關(guān)聯(lián)查詢。一般做關(guān)聯(lián)查詢,就自己單獨寫SQL就好了。

基于IDEA的mybatis逆向工程操作步驟如下

  1. 配置maven插件

    <build><plugins><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.7</version><configuration><!-- 輸出日志 --><verbose>true</verbose><overwrite>true</overwrite></configuration></plugin></plugins></build>
    
  2. 在resources目錄下創(chuàng)建名為generatorConfig.xml的配置文件

  3. 配置文件的模板如下

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfigurationPUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN""http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"><generatorConfiguration><!--導(dǎo)入屬性配置--><properties resource="properties/xx.properties"></properties><!-- 指定數(shù)據(jù)庫驅(qū)動的jdbc驅(qū)動jar包的位置 --><classPathEntry location="C:\Users\Vergi\.m2\repository\mysql\mysql-connector-java\8.0.11\mysql-connector-java-8.0.11.jar" /><!-- context 是逆向工程的主要配置信息 --><!-- id:起個名字 --><!-- targetRuntime:設(shè)置生成的文件適用于那個 mybatis 版本 --><context id="default" targetRuntime="MyBatis3"><!--optional,旨在創(chuàng)建class時,對注釋進行控制--><commentGenerator><property name="suppressDate" value="true" /><!-- 是否去除自動生成的注釋 true:是 : false:否 --><property name="suppressAllComments" value="true" /></commentGenerator><!--jdbc的數(shù)據(jù)庫連接--><jdbcConnection driverClass="${db.driver}"connectionURL="${db.url}"userId="${db.user}"password="${db.password}"></jdbcConnection><!--非必須,類型處理器,在數(shù)據(jù)庫類型和java類型之間的轉(zhuǎn)換控制--><javaTypeResolver><!-- 默認(rèn)情況下數(shù)據(jù)庫中的 decimal,bigInt 在 Java 對應(yīng)是 sql 下的 BigDecimal 類 --><!-- 不是 double 和 long 類型 --><!-- 使用常用的基本類型代替 sql 包下的引用類型 --><property name="forceBigDecimals" value="false" /></javaTypeResolver><!-- targetPackage:生成的實體類所在的包 --><!-- targetProject:生成的實體類所在的硬盤位置 --><javaModelGenerator targetPackage="mybatis.generator.model"targetProject=".\src\main\java"><!-- 是否允許子包 --><property name="enableSubPackages" value="false" /><!-- 是否清理從數(shù)據(jù)庫中查詢出的字符串左右兩邊的空白字符 --><property name="trimStrings" value="true" /></javaModelGenerator><!-- targetPackage 和 targetProject:生成的 mapper.xml 文件的包和位置 --><sqlMapGenerator targetPackage="mybatis.generator.mappers"targetProject=".\src\main\resources"><!-- 針對數(shù)據(jù)庫的一個配置,是否把 schema 作為字包名 --><property name="enableSubPackages" value="false" /></sqlMapGenerator><!-- targetPackage 和 targetProject:生成的 mapper接口文件的包和位置 --><javaClientGenerator type="XMLMAPPER"targetPackage="mybatis.generator.dao" targetProject=".\src\main\java"><!-- 針對 oracle 數(shù)據(jù)庫的一個配置,是否把 schema 作為子包名 --><property name="enableSubPackages" value="false" /></javaClientGenerator><!-- 這里指定要生成的表 --><table tableName="student"/><table tableName="product"/></context>
    </generatorConfiguration>
    
  4. 雙擊執(zhí)行mybatis-generator的maven插件

執(zhí)行日志如下

生成的文件如下

能看到mybatis-generator除了給我們生成了基本的PO類(上圖的Student和Product),還額外生成了Example類。Example類是為了方便執(zhí)行SQL時傳遞查詢條件的。使用的示例如下

public class GeneratorTest {private SqlSessionFactory sqlSessionFactory;@Beforepublic void init() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("mysql8-config.xml");sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);}@Testpublic void test() {SqlSession sqlSession = sqlSessionFactory.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);StudentExample example = new StudentExample();StudentExample.Criteria criteria = example.createCriteria();criteria.andNameLike("%o%");List<Student> students = mapper.selectByExample(example);students.forEach(System.out::println);}
}

結(jié)果如下

PageHelper分頁插件

使用該插件,快速實現(xiàn)查詢結(jié)果的分頁,使用步驟如下

  1. pom.xml中配置依賴

    <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.1.6</version>
    </dependency>
    
  2. mybatis全局配置文件中配置<plugin>標(biāo)簽

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration><properties resource="properties/xx.properties"></properties><plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"><property name="helperDialect" value="mysql"/></plugin></plugins><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${db.driver}"/><property name="url" value="${db.url}"/><property name="username" value="${db.user}"/><property name="password" value="${db.password}"/></dataSource></environment></environments><mappers><package name="mybatis.generator.dao"/></mappers></configuration>
    
  3. 在執(zhí)行查詢之前,先設(shè)置分頁信息

    // 查詢第一頁,每頁3條信息
    PageHelper.startPage(1,3);
    

    先看一下查所有數(shù)據(jù)

    	@Testpublic void test() {SqlSession sqlSession = sqlSessionFactory.openSession();ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);//PageHelper.startPage(1,3);List<Product> products = mapper.selectByExample(new ProductExample());products.forEach(System.out::println);}
    

    加上PageHelper分頁

    	@Testpublic void test() {SqlSession sqlSession = sqlSessionFactory.openSession();ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);PageHelper.startPage(1,3);List<Product> products = mapper.selectByExample(new ProductExample());products.forEach(System.out::println);}
    


    特別注意:在編寫mapper.xml的時候,SQL語句的結(jié)尾不要帶上;,因為PageHelper插件是在SQL末尾拼接LIMIT關(guān)鍵字來進行分頁的,若SQL語句帶上了;,就會造成SQL語法錯誤

    另外,PageHelper會先查詢總數(shù)量,然后再發(fā)出分頁查詢,打開mybatis的日志時,可以看到發(fā)出了2條SQL
    當(dāng)開啟PageHelper時,查詢得到的List實際是PageHelper中自定義的一個類Page,這個類實現(xiàn)了List接口,并封裝了分頁的相關(guān)信息(總頁數(shù),當(dāng)前頁碼等)。

    可以通過PageInfo來獲取分頁的相關(guān)信息,代碼如下

    @Test
    public void test() {SqlSession sqlSession = factory.openSession();PageHelper.startPage(1,3);ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);List<Product> list = mapper.findAll();list.forEach(System.out::println);PageInfo<Product> pageInfo = new PageInfo<>(list);System.out.println(pageInfo.getTotal()); // 獲得總數(shù)System.out.println(pageInfo.getPageSize());  // 獲得總頁數(shù)
    }
    

    PageHelper插件的源碼分析可以查看我之前的文章 =>
    極簡PageHelper源碼分析

Mybatis Plus

mybatis雖然非常方便,但也需要編寫大量的SQL語句,于是mybatis plus就應(yīng)運而生了。它是一個mybatis增強工具,為了簡化開發(fā),提高效率。搭配Spring-Boot食用簡直不要太爽。

可以參考我的這篇文章 mybatis-plus一發(fā)入魂 ,或者mybatis-plus官網(wǎng),以及慕課網(wǎng)的入門教程和進階教程

(完)

注:該文是一篇較為全面詳細的筆記,內(nèi)容篇幅很長。當(dāng)對mybatis的使用較為熟練后,可以查看這篇極為簡短的 mybatis精髓總結(jié),從整體架構(gòu)和源碼層面上把握mybatis。

總結(jié)

以上是生活随笔為你收集整理的mybatis看这一篇就够了,简单全面一发入魂的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。