mybatis看这一篇就够了,简单全面一发入魂
文章目錄
- Mybatis
-
- 概述
- 快速入門
-
- 原生開發(fā)示例
- 基于Mapper代理的示例
- 基于注解的示例
- 應(yīng)用場景
-
- 主鍵返回
- 批量查詢
- 動態(tài)SQL
- 緩存
- 關(guān)聯(lián)查詢
- 延遲加載
- 逆向工程
- PageHelper分頁插件
- Mybatis Plus
Mybatis
概述
-
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ā)
- 編寫全局配置文件
- 編寫mapper映射文件
- 加載全局配置文件,生成SqlSessionFactory
- 創(chuàng)建SqlSession,調(diào)用mapper映射文件中的SQL語句來執(zhí)行CRUD操作
原生開發(fā)示例
-
在本地虛擬機mysql上創(chuàng)建一個庫yogurt,并在里面創(chuàng)建一張student表
-
打開IDEA,創(chuàng)建一個maven項目
-
導(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> -
創(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;} -
編寫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> -
編寫數(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 -
編寫全局配置文件(主要是配置數(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> -
編寫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;} } -
測試
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é):
- 編寫mapper.xml,書寫SQL,并定義好SQL的輸入?yún)?shù),和輸出參數(shù)
- 編寫全局配置文件,配置數(shù)據(jù)源,以及要加載的mapper.xml文件
- 通過全局配置文件,創(chuàng)建SqlSessionFactory
- 每次進行CRUD時,通過SqlSessionFactory創(chuàng)建一個SqlSession
- 調(diào)用SqlSession上的
selectOne,selectList,insert,delete,update等方法,傳入mapper.xml中SQL標(biāo)簽的id,以及輸入?yún)?shù)
注意要點
-
全局配置文件中,各個標(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中需要使用
parameterType和resultType屬性來配置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ù)
resultType或resultMap的屬性來將查詢得到的結(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語句
-
-
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綁定起來
- mapper接口的全限定名,要和mapper.xml的namespace屬性一致
- mapper接口中的方法名要和mapper.xml中的SQL標(biāo)簽的id一致
- mapper接口中的方法入?yún)㈩愋?#xff0c;要和mapper.xml中SQL語句的入?yún)㈩愋鸵恢?/li>
- 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步,如下
-
創(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); } -
在全局配置文件中修改
<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);
將方法形參中的name和major放到了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呢?有兩種方式
-
使用
useGeneratedKeys和keyProperty屬性<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> -
使用
<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)簽的使用,注意如果parameterType是List的話,則在標(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> -
bindmybatis的動態(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逆向工程操作步驟如下
-
配置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> -
在resources目錄下創(chuàng)建名為generatorConfig.xml的配置文件
-
配置文件的模板如下
<?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> -
雙擊執(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é)果的分頁,使用步驟如下
-
pom.xml中配置依賴
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.1.6</version> </dependency> -
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> -
在執(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)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小G有一个大树
- 下一篇: 男士领带怎么打 男士领带打法教程