Mybatis持久层开发
文章目錄
- Mybatis持久層開發簡要步驟
- 概述:
- 示例:
- 總結:
- 注意要點:
- 基于Mapper代理的示例
- 基于注解的示例
- 應用場景:
- 主鍵返回
- 批量查詢
- 動態SQL
- 緩存
- 關聯查詢
- 延遲加載
- 逆向工程
- PageHelper分頁插件
- Mybatis Plus
Mybatis持久層開發簡要步驟
概述:
1.編寫全局配置文件
2.編寫mapper映射文件
3.加載全局配置文件,生成SqlSessionFactory
4.創建SQLSession,調用mapper映射文件中的SQL語句來執行CRUD操作
注:CRUD是4個單詞的首字母,CRUD分別指增加(Create)、讀取查詢(Retrieve)、更新(Update)和刪除(Delete)這4個單詞的首字母。
示例:
1.使用MySQL命令行或某些數據庫可視化軟件創建一張student表
2.打開IDEA創建一個Maven項目
3.導入依賴的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.創建一個類
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.編寫數據源jdbc.properties文件
db.url=jdbc:mysql://192.168.183.129:3306/yogurt?characterEncoding=utf8 db.user=root db.password=root db.driver=com.mysql.jdbc.Driver7.編寫全局配置文件(主要是配置數據源信息),命名為mybatis.properties
<?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="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);} }總結:
注意要點:
1.全局配置文件中,各標簽配置順序如下,因為mybatis中的源碼是按此順序進行解析
<configuration><!-- 配置順序如下properties settingstypeAliasestypeHandlersobjectFactorypluginsenvironmentsenvironmenttransactionManagerdataSourcemappers--> </configuration>子標簽說明:
- <properties>
一般將數據源的信息單獨放在一個properties文件中,然后用這個標簽引入,在下面environment標簽中,就可以用${}占位符快速獲取數據源的信息
- <settings>
用來開啟或關閉mybatis的一些特性,比如可以用<setting name="lazyLoadingEnabled" value="true"/>來開啟延遲加載,可以用<settings name="cacheEnabled" value="true"/>來開啟二級緩存
- <typeAliases>
在mapper.xml中需要使用parameterType和resultType屬性來配置SQL語句的輸入參數類型和輸出參數類型,類必須要寫上全限定名,比如一個SQL的返回值映射為Student類,則resultType屬性要寫com.yogurt.po.Student,這太長了,所以可以用別名來簡化書寫,比如
<typeAliases><typeAlias type="com.yogurt.po.Student" alias="student"/> </typeAliases>之后就可以在resultType上直接寫student,mybatis會根據別名配置自動找到對應的類。
當然,如果想要一次性給某個包下的所有類設置別名,可以用如下的方式
<typeAliases><package name="com.yogurt.po"/> </typeAliases>如此,指定包下的所有類,都會以簡單類名的小寫形式,作為它的別名
另外,對于基本的Java類型 -> 8大基本類型以及包裝類,以及String類型,mybatis提供了默認的別名,別名為其簡單類名的小寫,比如原本需要寫java.lang.String,其實可以簡寫為string
-
<typeHandlers>
用于處理Java類型和Jdbc類型之間的轉換,mybatis有許多內置的TypeHandler,比如StringTypeHandler,會處理Java類型String和Jdbc類型CHAR和VARCHAR。這個標簽用的不多
-
<objectFactory>
mybatis會根據resultType或resultMap的屬性來將查詢得到的結果封裝成對應的Java類,它有一個默認的DefaultObjectFactory,用于創建對象實例,這個標簽用的也不多
-
<plugins>
可以用來配置mybatis的插件,比如在開發中經常需要對查詢結果進行分頁,就需要用到pageHelper分頁插件,這些插件就是通過這個標簽進行配置的。在mybatis底層,運用了責任鏈模式+動態代理去實現插件的功能
<!-- PageHelper 分頁插件 --> <plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"><property name="helperDialect" value="mysql"/></plugin> </plugins> -
<environments>
用來配置數據源
-
<mappers>
用來配置mapper.xml映射文件,這些xml文件里都是SQL語句
2.mapper.xml的SQL語句中的占位符${}和#{}
一般會采用#{},#{}在mybatis中,最后會被解析為?,其實就是Jdbc的PreparedStatement中的?占位符,它有預編譯的過程,會對輸入參數進行類型解析(如果入參是String類型,設置參數時會自動加上引號),可以防止SQL注入,如果parameterType屬性指定的入參類型是簡單類型的話(簡單類型指的是8種java原始類型再加一個String),#{}中的變量名可以任意,如果入參類型是pojo,比如是Student類
那么#{name}表示取入參對象Student中的name屬性,#{age}表示取age屬性,這個過程是通過反射來做的,這不同于${},${}取對象的屬性使用的是OGNL(Object Graph Navigation Language)表達式
而${},一般會用在模糊查詢的情景,比如SELECT * FROM student WHERE name like '%${name}%';
它的處理階段在#{}之前,它不會做參數類型解析,而僅僅是做了字符串的拼接,若入參的Student對象的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'%'; 所以模糊查詢只能用${},雖然普通的入參也可以用${},但由于${}不會做類型解析,就存在SQL注入的風險,比如
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的入參,${}中獲取對象屬性的語法和#{}幾乎一樣,但${}在mybatis底層是通過OGNL表達式語言進行處理的,這跟#{}的反射處理有所不同
對于簡單類型(8種java原始類型再加一個String)的入參,${}中參數的名字必須是value
上面其實是比較原始的開發方式,我們需要編寫dao類,針對mapper.xml中的每個SQL標簽,做一次封裝,SQL標簽的id要以字符串的形式傳遞給SqlSession的相關方法,容易出錯,非常不方便;為了簡化開發,mybatis提供了mapper接口代理的開發方式,不需要再編寫dao類,只需要編寫一個mapper接口,一個mapper的接口和一個mapper.xml相對應,只需要調用SqlSession對象上的getMapper(),傳入mapper接口的class信息,即可獲得一個mapper代理對象,直接調用mapper接口中的方法,即相當于調用mapper.xml中的各個SQL標簽,此時就不需要指定SQL標簽的id字符串了,mapper接口中的一個方法,就對應了mapper.xml中的一個SQL標簽
基于Mapper代理的示例
全局配置文件和mapper.xml文件是最基本的配置,仍然需要。這次不編寫dao類,直接創建一個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之間需要遵循一定規則,才能成功的讓mybatis將mapper接口和mapper.xml綁定起來
這個mapper接口,mybatis會自動找到對應的mapper.xml,然后對mapper接口使用動態代理的方式生成一個代理類
基于注解的示例
創建一個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>標簽,直接指定加載這個類
<?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);} }注:當使用注解開發時,若需要傳入多個參數,可以結合@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標簽會被mybatis處理并封裝成一個Map對象,比如上面的示例中,實際傳入的參數是一個Map對象,@Param標簽幫忙向Map中設置了值,即它做了
Map<String,Object> map = new HashMap<>(); map.put("name", name); map.put("major",major);將方法形參中的name和major放到了map對象中,所以在@Select標簽中可以用${name}和${major}取出map對象中的值。
三種加載mapper的方式總結
-
<mapper resource="" />
加載普通的xml文件,傳入xml的相對路徑(相對于類路徑)
-
<mapper class="" />
使用mapper接口的全限定名來加載,若mapper接口采用注解方式,則不需要xml;若mapper接口沒有采用注解方式,則mapper接口和xml文件的名稱要相同,且在同一個目錄
-
<package name="" />
掃描指定包下的所有mapper,若mapper接口采用注解方式,則不需要xml;若mapper接口沒有采用注解方式,則mapper接口和xml文件的名稱要相同,且在同一目錄
注意:用后兩種方式加載mapper接口和mapper.xml映射文件時,可能會報錯
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-VjPtAEa1-1646826930975)(C:\Users\20490\AppData\Roaming\Typora\typora-user-images\image-20220220193746535.png)]
是因為,對于src/main/java 源碼目錄下的文件,maven打包時只會將該目錄下的java文件打包,而其他類型的文件都不會被打包進去,去工程目錄的target目錄下看看maven構建后生成的文件,發現mapper.xml未被打包進去
具體解決辦法見(4條消息) 關于maven打包時, 資源文件沒有被打包進來的問題_vcj1009784814的博客-CSDN博客_maven打包resource文件沒打進去
應用場景:
主鍵返回
通常我們會將數據庫表的主鍵id設為自增。在插入一條記錄時,我們不設置其主鍵id,而讓數據庫自動生成該條記錄的主鍵id,那么在插入一條記錄后,如何得到數據庫自動生成的這條記錄的主鍵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> 123使用<selectKey>子標簽
<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這樣的支持自增主鍵的數據庫,可以簡單的使用第一種方式;對于不支持自增主鍵的數據庫,如oracle,則沒有主鍵返回這一概念,而需要在插入之前先生成一個主鍵。此時可以用<selectKey>標簽,設置其order屬性為BEFORE,并在標簽體內寫上生成主鍵的SQL語句,這樣在插入之前,會先處理<selectKey>,生成主鍵,再執行真正的插入操作。
<selectKey>標簽其實就是一條SQL,這條SQL的執行,可以放在主SQL執行之前或之后,并且會將其執行得到的結果封裝到入參的Java對象的指定屬性上。注意<selectKey>子標簽只能用在<insert>和<update>標簽中。上面的LAST_INSERT_ID()實際上是MySQL提供的一個函數,可以用來獲取最近插入或更新的記錄的主鍵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());} }批量查詢
主要是動態SQL標簽的使用,注意如果parameterType是List的話,則在標簽體內引用這個List,只能用變量名list,如果parameterType是數組,則只能用變量名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);}動態SQL
可以根據具體的參數條件,來對SQL語句進行動態拼接。
比如在以前的開發中,由于不確定查詢參數是否存在,許多人會使用類似于where 1 = 1 來作為前綴,然后后面用AND 拼接要查詢的參數,這樣,就算要查詢的參數為空,也能夠正確執行查詢,如果不加1 = 1,則如果查詢參數為空,SQL語句就會變成SELECT * FROM student where,SQL不合法。
mybatis里的動態標簽主要有
-
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>當滿足test條件時,才會將<if>標簽內的SQL語句拼接上去
-
choose
<!-- choose 和 when , otherwise 是配套標簽 類似于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>標簽只會在至少有一個子元素返回了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>標簽可以用<trim>標簽代替
<trim prefix="WHERE" prefixOverrides="AND | OR">... </trim> -
set
在至少有一個子元素返回了SQL語句時,才會向SQL語句中添加SET,并且如果SET之后是以,開頭的話,會自動將其刪掉
<set>標簽相當于如下的<trim>標簽
<trim prefix="SET" prefixOverrides=",">... </trim>
可以通過<trim>標簽更加靈活地對SQL進行定制
實際上在mybatis源碼,也能看到trim與set,where標簽的父子關系
-
-
foreach
用來做迭代拼接的,通常會與SQL語句中的IN查詢條件結合使用,注意,到parameterType為List(鏈表)或者Array(數組),后面在引用時,參數名必須為list或者array。如在foreach標簽中,collection屬性則為需要迭代的集合,由于入參是個List,所以參數名必須為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
可將重復的SQL片段提取出來,然后在需要的地方,使用<include>標簽進行引用
<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的動態SQL都是用OGNL表達式進行解析的,如果需要創建OGNL表達式以外的變量,可以用bind標簽
<select id="selectBlogsLike" resultType="Blog"><bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />SELECT * FROM BLOGWHERE title LIKE #{pattern} </select>
緩存
-
一級緩存
默認開啟,同一個SqlSesion級別共享的緩存,在一個SqlSession的生命周期內,執行2次相同的SQL查詢,則第二次SQL查詢會直接取緩存的數據,而不走數據庫,當然,若第一次和第二次相同的SQL查詢之間,執行了DML(INSERT/UPDATE/DELETE),則一級緩存會被清空,第二次查詢相同SQL仍然會走數據庫
一級緩存在下面情況會被清除
- 在同一個SqlSession下執行增刪改操作時(不必提交),會清除一級緩存
- SqlSession提交或關閉時(關閉時會自動提交),會清除一級緩存
- 對mapper.xml中的某個CRUD標簽,設置屬性flushCache=true,這樣會導致該MappedStatement的一級緩存,二級緩存都失效(一個CRUD標簽在mybatis中會被封裝成一個MappedStatement)
- 在全局配置文件中設置 <setting name="localCacheScope" value="STATEMENT"/>,這樣會使一級緩存失效,二級緩存不受影響
-
二級緩存
默認關閉,可通過全局配置文件中的<settings name="cacheEnabled" value="true"/>開啟二級緩存總開關,然后在某個具體的mapper.xml中增加<cache />,即開啟了該mapper.xml的二級緩存。二級緩存是mapper級別的緩存,粒度比一級緩存大,多個SqlSession可以共享同一個mapper的二級緩存。注意開啟二級緩存后,SqlSession需要提交,查詢的數據才會被刷新到二級緩存當中
關聯查詢
使用<resultMap> 標簽以及<association>和<collection> 子標簽,進行關聯查詢,比較簡單
延遲加載
延遲加載是結合關聯查詢進行應用的。也就是說,只在<association>和<collection> 標簽上起作用
對于關聯查詢,若不采用延遲加載策略,而是一次性將關聯的從信息都查詢出來,則在主信息比較多的情況下,會產生N+1問題,導致性能降低。比如用戶信息和訂單信息是一對多的關系,在查詢用戶信息時,設置了關聯查詢訂單信息,如不采用延遲加載策略,假設共有100個用戶,則我們查這100個用戶的基本信息只需要一次SQL查詢
select * from user;若開啟了關聯查詢,且不是延遲加載,則對于這100個用戶,會發出100條SQL去查用戶對應的訂單信息,這樣會造成不必要的性能開銷(其實我認為稱之為1+N問題更為合適)
select * from orders where u_id = 1; select * from orders where u_id = 2; .... select * from orders where u_id = 100;當我們可能只關心id=3的用戶的訂單信息,則很多的關聯信息是無用的,于是,采用延遲加載策略,可以按需加載從信息,在需要某個主信息對應的從信息時,再發送SQL去執行查詢,而不是一次性全部查出來,這樣能很好的提升性能。
另外,針對N+1問題,除了采用延遲加載的策略按需進行關聯查詢。如果在某些場景下,確實需要查詢所有主信息關聯的從信息。在上面的例子中,就是如果確實需要把這100個用戶關聯的訂單信息全部查詢出來,那怎么辦呢?這里提供2個解決思路。
1是采用連接查詢,只使用1條SQL即可,如下
select * from user as u left join orders as o on u.id = o.u_id;但使用連接查詢查出來的結果是兩表的笛卡爾積,還需要自行進行數據的分組處理
2是使用兩個步驟來完成,先執行一條SQL,查出全部的用戶信息,并把用戶的id放在一個集合中,然后第二條SQL采用IN關鍵字查詢即可。這種方式也可以簡化為子查詢,如下
select * from orders where u_id in (select id from user);現在說回來,mybatis的延遲加載默認是關閉的,可以通過全局配置文件中的<setting name="lazyLoadingEnabled" value="true"/>來開啟,開啟后,所有的SELECT查詢,若有關聯對象,都會采用延遲加載的策略。當然,也可以對指定的某個CRUD標簽單獨禁用延遲加載策略,通過設置SELECT標簽中的fetchType=eager,則可以關閉該標簽的延遲加載。
(還有一個侵入式延遲加載的概念,在配置文件中通過<setting name="aggressiveLazyLoading" value="true">來開啟,大概是說,訪問主對象中的主信息時,就會觸發延遲加載,將從信息查詢上來,這其實并不是真正意義的延遲加載,真正意義上的延遲加載應該是訪問主對象中的從信息時,才觸發延遲加載,去加載從信息,侵入式延遲加載默認是關閉的,一般情況下可以不用管他)
注意,延遲加載在關聯查詢的場景下才有意義。需要配合<resultMap>標簽下的<association>和<collecction> 標簽使用
<!-- 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"/><!-- 當延遲加載總開關開啟時,resultMap下的association和collection標簽中,若通過select屬性指定嵌套查詢的SQL,則其fetchType默認是lazy的,當在延遲加載總開關開啟時,需要對個別的關聯查詢禁用延遲加載時,才有必要配置fetchType = eager --><!--column用于指定用于關聯查詢的列property用于指定要封裝到StudentExt中的哪個屬性javaType用于指定關聯查詢得到的對象select用于指定關聯查詢時,調用的是哪一個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> /** 用于封裝關聯查詢的對象 **/ public class StudentExt{private Integer id;private String name;private Integer score;private Integer age;private Integer gender;/** 關聯對象 **/private Clazz clazz;//getter/setter }逆向工程
PageHelper分頁插件
Mybatis Plus
mybatis雖然非常方便,但也需要編寫大量的SQL語句,于是mybatis plus就應運而生了。它是一個mybatis增強工具,為了簡化開發,提高效率。搭配Spring-Boot食用簡直不要太爽。
可以參考這篇文章 mybatis-plus一發入魂
注:該文是一篇較為全面詳細的筆記,內容篇幅很長。當對mybatis的使用較為熟練后,可以查看這篇極為簡短的 mybatis精髓總結,從整體架構和源碼層面上把握mybatis。
轉載從(4條消息) mybatis看這一篇就夠了,簡單全面一發入魂_vcj1009784814的博客-CSDN博客_mybatis看這一篇
總結
以上是生活随笔為你收集整理的Mybatis持久层开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 下级对上级认可应该用什么词_下级对上级的
- 下一篇: vbs脚本学习笔记