MyBatis入門學習教程-MyBatis快速入門
Mybatis
MyBatis ,是國內(nèi)最火的持久層框架
采用了ORM思想解決了實體類和數(shù)據(jù)庫表映射的問題。對JDBC進行了封裝,屏蔽了JDBCAPI底層的訪問細節(jié),避免我們與jdbc的api打交 道,就能完成對數(shù)據(jù)的持久化操作。
O--Object java對象
R- Relation 關(guān)系,就是數(shù)據(jù)庫中的一張表
M-mapping 映射
一、快速開始
Mybatis 官方幫助文檔: https://mybatis.org/mybatis-3/zh/index.html
1、創(chuàng)建 Maven 項目
2、導(dǎo)入 Maven 依賴
這里,我們要導(dǎo)入 mybatis的依賴、mysql 的一類、單元測試的依賴
<dependencies> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.23</version> </dependency> <!--單元測試--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
3、配置 Maven 插件
這里,要配置的,是 Maven 的編譯插件,我們指定源文件和編譯后的文件都是 java 1.8 版本的
<build> <plugins> <!--編譯插件--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
4、新建數(shù)據(jù)庫,導(dǎo)入表格
CREATE TABLE `team` ( `teamId` int NOT NULL AUTO_INCREMENT COMMENT '球隊ID', `teamName` varchar(50) DEFAULT NULL COMMENT '球隊名稱', `location` varchar(50) DEFAULT NULL COMMENT '球隊位置', `createTime` date DEFAULT NULL COMMENT '球隊建立時間', PRIMARY KEY (`teamId`) ) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
5、編寫 Mybatis 配置文件
我們可以直接在官網(wǎng),獲取配置文件的示例
日后在開發(fā)時,建議讀者創(chuàng)建一個文檔,存放配置文件的編寫規(guī)則,方便開發(fā)
<property>中的內(nèi)容,可以通過配置文件的方式獲取,我們后面再介紹
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--mybatis 環(huán)境,可以為 mybatis 配置多套環(huán)境,然后根據(jù) id 切換--> <environments default="development"> <environment id="development"> <!--事務(wù)類型:使用 JDBC 事務(wù),使用 Connection 的提交和回滾--> <transactionManager type="JDBC"/> <!--數(shù)據(jù)源 dataSource:創(chuàng)建數(shù)據(jù)庫 Connection 對象 type: POOLED 使用數(shù)據(jù)庫的連接池 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis_study?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT"/> <property name="username" value="admin"/> <property name="password" value="123"/> </dataSource> </environment> </environments> <!--mapper映射,這里我們先不寫,后面再講--> <!--<mappers>--> <!-- <mapper resource="org/mybatis/example/BlogMapper.xml"/>--> <!--</mappers>--> </configuration>
6、編寫實體類
@AllArgsConstructor @NoArgsConstructor @Data public class Team { private Integer teamId; private String teamName; private String location; private Date createTime; }
7、編寫 mapper 接口
public interface TeamMapper { /** * 獲取全部 team 信息 * @return */ List<Team> getAll(); }
8、編寫 mapper 實現(xiàn)
實現(xiàn),是用 .xml 文件編寫的
id:接口中非方法
resultTyoe:接口方法的返回值(在配置前,必須寫全類名)
注意: XxxMapper.xml,必須要和對應(yīng)的 mapper 接口,同包同名
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--namespace綁定一個指定的Dao/Mapper接口--> <mapper namespace="top.faroz.dao.TeamMapper"> <select id="getAll" resultType="top.faroz.pojo.Team"> select * from team </select> </mapper>
也可以將 .xml 實現(xiàn),放在 resources 下,但是一定要注意,同包同名:
9、Mybatis 配置文件中,添加 mapper 映射
<!--mapper映射--> <mappers> <package name="top.faroz.dao"/> </mappers>
編寫完 .xml 實現(xiàn)后,一定要在配置文件中,添加 mapper 映射
<!--mapper映射--> <mappers> <package name="top.faroz.dao"/> </mappers>
10、編寫 Mybatis 工具類
工具類用來創(chuàng)建 sqlSession的單例工廠
并添加一個方法,用來獲取 sqlSession 連接
public class MybatisUtil { /** * 連接工廠 * 用來創(chuàng)建連接 */ private static SqlSessionFactory sqlSessionFactory; static { //使用MyBatis第一步:獲取SqlSessionFactory對象 try { String resource = "mybatis.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); /** * 通過配置文件,創(chuàng)建工程 * (配置文件就是 resources 下的 mybatis.xml) */ sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession() { //設(shè)置為true,自動提交事務(wù) return sqlSessionFactory.openSession(true); } }
11、測試
@Test public void getAllTest() { SqlSession sqlSession = MybatisUtil.getSqlSession(); TeamMapper mapper = sqlSession.getMapper(TeamMapper.class); List<Team> all = mapper.getAll(); for (Team team : all) { System.out.println(team); } sqlSession.close(); }
測試結(jié)果如下:
二、日志添加
1、添加 Maven 依賴
<!--log4j日志--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
2、添加 log4j 配置
log4j.properties
我們可以調(diào)整日志輸出的級別,除了 DEBUG外,還可以有 INFO,WARNING , ERROR
# Global logging configuration info warning error log4j.rootLogger=DEBUG,stdout # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
現(xiàn)在是在控制臺輸出,未來,可能我們的項目,是要部署在客戶的服務(wù)器上的,我們可以將日志信息固定輸出到某個外部文件當中
3、Mybatis 中配置 LOG
<!--配置日志--> <settings> <setting name="logImpl" value="LOG4J"/> </settings>
4、執(zhí)行測試
控制臺會顯示詳細的日志信息
三、Mybatis 對象分析
1、Resources
Resources 類,顧名思義就是資源,用于讀取資源文件。其有很多方法通過加載并解析資源文件,返回不同類型的 IO 流對象。
2、SqlSessionFactoryBuilder
用來創(chuàng)建 SqlSessionFactory 的創(chuàng)造者,在整個項目中,只會使用一次,就是用來創(chuàng)建 SqlSessionFactory 的。用后即丟
3、SqlSessionFactory
創(chuàng)建 sqlSession 的單例工廠。在整個項目中,應(yīng)該只有一個 SqlSessionFactory 的單例
4、SqlSession
一個 SqlSession 對應(yīng)著一次數(shù)據(jù)庫會話,一次會話以SqlSession 對象的創(chuàng)建開始,以 SqlSession 對象的關(guān)閉結(jié)束。
SqlSession 接口對象是線程不安全的,所以每次數(shù)據(jù)庫會話結(jié)束前,需要馬上調(diào)用其 close()方法,將其關(guān)閉。再次需要會話,再次創(chuàng)建。
四、改進 MybatisUtil
在 快速開始 欄目中,我們將獲取 Mybatis 的 sqlSession 對象的過程,封裝成了一個工具類
但是,sqlSession 并沒有作為一個成員變量,存儲在 MybatisUtil 中,這樣,我們對 sqlSession 執(zhí)行 close ,必須要手動去調(diào)用我們外部獲得的 sqlSession ,其實,我們可以將 sqlSession 變成一個成員變量,然后,在 MybatisUtil 中,寫一個close 方法。
但是,我們要注意,sqlSession 是線程不安全的,每次使用完后,我們都需要進行 close,然后,在下次使用的時候,再次連接 sqlSession ,如果把 sqlSession ,作為一個靜態(tài)成員變量,放在 MybatisUtil 中,勢必會引發(fā)線程相關(guān)的問題。
為了解決這個問題,我們需要來介紹一下 ThreadLocal
1、ThreadLocal
2、使用 ThreadLocal 來改寫
3、小結(jié)
我個人其實是不建議將 sqlSession 作為一個靜態(tài)成員變量的,并且官方文檔也不建議我們這么做。我寫這一小節(jié)的主要目的,還是為了介紹一下 ThreadLocal
五、輸入映射
之前,我們的測試中,輸入都是單個值,如果我們的輸入中,有多個值,該怎么辦呢?
1、使用下標(不推薦)
如果有多個參數(shù),可以使用:
#{arg0},#{arg1},#{arg2}…,或者#{param0},#{param2},#{param3}…的方式,獲取不同參數(shù)
但是這種方式,不推薦使用
2、使用注解
我們可以在對應(yīng)的 mapper 接口的參數(shù)前,加上 @Param 注解,并且在 .xml 實現(xiàn)類中,直接使用注解中定義的參數(shù)名
接口:
Team selectByNameAndLocation(@Param("name") String name,@Param("location") String location);
實現(xiàn):
<select id="selectByNameAndLocation" resultType="top.faroz.pojo.Team"> select * from team where teamName=#{name} and location=#{location} </select>
測試:
@Test public void selectByNameAndLocationTest() { SqlSession sqlSession = MybatisUtil.getSqlSession(); TeamMapper mapper = sqlSession.getMapper(TeamMapper.class); Team team = mapper.selectByNameAndLocation("雄鹿", "威斯康星州密爾沃基"); System.out.println(team); sqlSession.close(); }
3、使用 map
傳入的參數(shù),可以為 map ,.xml文件中,獲取的參數(shù),要和 map 中的 key 值保持一致
接口:
Team selectByName(Map map);
實現(xiàn):
<select id="selectByName" resultType="top.faroz.pojo.Team" parameterType="map"> select * from team where teamName=#{name} </select>
測試:
@Test public void selectByNameTest() { SqlSession sqlSession = MybatisUtil.getSqlSession(); TeamMapper mapper = sqlSession.getMapper(TeamMapper.class); Map<String, Object> map = new HashMap<>(); map.put("name","雄鹿"); Team team = mapper.selectByName(map); System.out.println(team); sqlSession.close(); }
六、輸出映射
1、resultType
1)、輸出簡單類型
就是一般的,輸出單個 String 或者 Integer 類型,前面的示例有很多,這里不再演示了
2)、輸出 pojo 類型
List<Team> queryAll();
<!--接口方法返回是集合類型,但是映射文件中的resultType需要指定集合中的類型,不是集合本身。--> <select id="queryAll" resultType="com.kkb.pojo.Team"> select * from team; </select>
3)、輸出 map 類型
當我們只需要查詢表中幾列數(shù)據(jù)的時候可以將sql的查詢結(jié)果作為Map的key和value。一般使用的是Map<Object,Object>。
Map 作為接口返回值,sql 語句的查詢結(jié)果最多只能有一條記錄。大于一條記錄會拋出TooManyResultsException異常。 如果有多行,使用List<Map<Object,Object>>。
Map<Object,Object> queryTwoColumn(int teamId); List<Map<Object,Object>> queryTwoColumnList();
<select id="queryTwoColumn" resultType="java.util.HashMap" paramType="int"> select teamName,location from team where teamId = #{id} </select> <select id="queryTwoColumnList" resultType="java.util.HashMap"> select teamName,location from team </select>
@Test public void test08(){ Map<String, Object> map = teamMapper.queryTwoColumn(); System.out.println(map); } @Test public void test09(){ List<Map<String, Object>> list = teamMapper.queryTwoColumnList(); for (Map<String, Object> map : list) { System.out.println(map); } }
2、resultMap
resultMap 可以自定義 sql 的結(jié)果和 java 對象屬性的映射關(guān)系。更靈活的把列值賦值給指定屬性。
一般主鍵列用id , 其余列用result column:表示數(shù)據(jù)庫表中的列名,不區(qū)分大小寫 property:表示實體類中的對應(yīng)的屬性名,區(qū)分大小寫 javaType: 實體類中的對應(yīng)的屬性的類型,可以省略,mybatis會自己推斷 jdbcType="數(shù)據(jù)庫中的類型column的類型" 一般省略
<resultMap id="baseResultMap" type="com.kkb.pojo.Team"> <!--主鍵列,使用 id--> <id column="teamId" property="teamId" javaType="java.lang.Integer"></id> <!--其余列,使用 result--> <result column="teamName" property="teamName" javaType="java.lang.String"></result> <result column="location" property="location" javaType="java.lang.String"></result> <result column="createTime" property="createTime" javaType="java.util.Date"></result> </resultMap>
實現(xiàn)語句,使用的時候,返回值就由原來的 resultType,改為我們寫的 resultMap:
<select id="queryAll2" resultMap="baseResultMap"> select * from team; </select>
使用 resultMap 進行屬性映射,還可以解決屬性名與數(shù)據(jù)庫表列名不一致的問題
3、數(shù)據(jù)庫表中列與實體類屬性不一致的處理方式
1)、使用 resultMap 去解決
上面講過,這里就不再演示了
2)、sql 中起別名
假如 sql 中的命名方式,是使用下劃線方式的,而 pojo 中,使用的是駝峰命名方式,那我們可以用如下起別名的方式,將查詢出的結(jié)果,換成駝峰命名方式:
select user_id as userId,user_name as userName,user_age as userAge from users where user_id=#{id};
七、#{} 和 ${} 的區(qū)別
這個問題,也是面試題常考的
#{}:表示一個占位符,通知Mybatis 使用實際的參數(shù)值代替。并使用 PrepareStatement 對象執(zhí)行 sql 語句, #{…}代替sql 語句的“?”。這個是Mybatis 中的首選做法,安全迅速。
通過這種方式,可以防止 sql 注入。
${}:表示字符串原樣替換,通知Mybatis 使用$包含的“字符串”替換所在位置。使用 Statement或者PreparedStatement 把 sql 語句和${} 的內(nèi)容連接起來。一般用在替換表名, 列名,不同列排序等操作。
這種方式,可以修改 sql 語句的列,比如說,我們有這么個需求:
需要分別根據(jù) name,age,address 等查詢用戶信息,按照一般的寫法,就要寫 3 個 sql
select * from xxx where name = #{name} select * from xxx where age = #{age} select * from xxx where address = #{address}
我們可以看到,不同的,只是 where 后面的部分,這里,我們就可以用 ${}去替換 where 后面的部分,從而不用寫那么多 sql
select * from xxx where ${column} = #{columnValue}
這里要注意,因為是字符串拼接非方式,所以,${}千萬不能用在參數(shù)上面,不然可能會產(chǎn)生 sql 注入的問題。但為什么可以用在 sql 的其他位置?這是因為,如果在非參數(shù)的地方,寫上 sql 注入的語句,就會造成 sql 語句錯誤,從而報錯。
八、Mybatis 全局配置文件
1、配置內(nèi)容總覽
配置的時候,少幾個配置沒有關(guān)系,但是一定要按照下面的順去配置
configuration(配置) properties--屬性:加載外部的配置文件,例如加載數(shù)據(jù)庫的連接信息 Settings--全局配置參數(shù):例如日志配置 typeAliases--類型別名 typeHandlers----類型處理器 objectFactory-----對象工廠 Plugins------插件:例如分頁插件 Environments----環(huán)境集合屬性對象 environment(環(huán)境變量) transactionManager(事務(wù)管理器) dataSource(數(shù)據(jù)源) Mappers---映射器:注冊映射文件用
2、屬性(properties)
可以通過屬性配置,讓 Mybatis 去讀取外部的配置文件,比如加載數(shù)據(jù)庫的連接信息
1)、新建配置文件
在 resources 文件夾下,建立 jdbc.properties 配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis_study?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT jdbc.username=admin jdbc.password=123
2)、mybatis 配置中,引入配置文件信息
<!--加載配置文件--> <properties resource="jdbc.properties"/>
3)、讀取配置文件
<property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/>
3、設(shè)置(settings)
MyBatis 中極為重要的調(diào)整設(shè)置,它們會改變 MyBatis 的運行時行為.例如我們配置的日志就是應(yīng)用之一。其余內(nèi)容參考設(shè)置文檔
https://mybatis.org/mybatis-3/zh/configuration.html#settings
4、類型別名(typeAliases)
類型別名可為 Java 類型設(shè)置一個縮寫名字。 它僅用于 XML 配置,意在降低冗余的全限定類名書寫。
1)已支持的別名
2)自定義別名
類型別名可為 Java 類型設(shè)置一個縮寫名字。 它僅用于 XML 配置,意在降低冗余的全限定類名書寫。例如:
<typeAliases> <typeAlias alias="Author" type="domain.blog.Author"/> <typeAlias alias="Blog" type="domain.blog.Blog"/> <typeAlias alias="Comment" type="domain.blog.Comment"/> <typeAlias alias="Post" type="domain.blog.Post"/> <typeAlias alias="Section" type="domain.blog.Section"/> <typeAlias alias="Tag" type="domain.blog.Tag"/> </typeAliases>
當這樣配置時,Blog 可以用在任何使用 domain.blog.Blog 的地方。
(下面👇這個方法應(yīng)該更便捷)
也可以指定一個包名,MyBatis 會在包名下面搜索需要的 Java Bean,比如:
<typeAliases> <package name="top.faroz.pojo"/> </typeAliases>
<select id="getUserList" resultType="user"> select * from user </select>
5、 映射器(Mappers)
方式一:【推薦使用】
<!-- 使用相對于類路徑的資源引用 --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers>
方式二:使用class文件綁定
<!-- 使用映射器接口實現(xiàn)類的完全限定類名 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers>
注意點:
接口和配置文件同名接口和它的Mapper配置文件必須在同一個包下
方式三:使用掃描包進行注入綁定
<!-- 將包內(nèi)的映射器接口實現(xiàn)全部注冊為映射器 --> <mappers> <package name="org.mybatis.builder"/> </mappers>
6、數(shù)據(jù)源(dataSource)
有三種內(nèi)建的數(shù)據(jù)源類型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
UNPOOLED– 這個數(shù)據(jù)源的實現(xiàn)會每次請求時打開和關(guān)閉連接。雖然有點慢,但對那些數(shù)據(jù)庫連接可用性要求不高的簡單應(yīng)用程序來說POOLED– 這種數(shù)據(jù)源的實現(xiàn)利用“池”的概念將 JDBC 連接對象組織起來,避免了創(chuàng)建新的連接實例時所必需的初始化和認證時間。 (數(shù)據(jù)庫連接池)JNDI – 這個數(shù)據(jù)源實現(xiàn)是為了能在如 EJB 或應(yīng)用服務(wù)器這類容器中使用,容器可以集中或在外部配置數(shù)據(jù)源,然后放置一個 JNDI 上下文的數(shù)據(jù)源引用。
默認使用POOLED
7、事務(wù)(transactionManager
)
1)、默認是需要手動提交事務(wù)的
Mybatis 框架是對 JDBC 的封裝,所以 Mybatis 框架的事務(wù)控制方式,本身也是用 JDBC 的 Connection對象的 commit(), rollback().Connection 對象的 setAutoCommit()方法來 設(shè)置事務(wù)提交方式的。
自動提交和手工提交、<transactionManager type="JDBC"/>該標簽用于指定 MyBatis所使用的事務(wù)管理器。MyBatis 支持兩種事務(wù)管理器類型:JDBC 與 MANAGED。
JDBC:使用JDBC的事務(wù)管理機制,通過Connection對象的 commit()方法提交,通過rollback()方法 回滾。默認情況下,mybatis將自動提交功能關(guān)閉了,改為了手動提交,觀察日志可以看出,所以我們在程序中都需要自己提交事務(wù)或者回滾事務(wù)。
MANAGED:由容器來管理事務(wù)的整個生命周期(如Spring容器)。
2)、自動提交事務(wù)
九、關(guān)系映射
1、對一關(guān)系
有這么一個需求,有許多學生,很多學生對應(yīng)某個老師,現(xiàn)在要將學生和對應(yīng)老師的屬性查詢出來
SQL語句:
# 學生表 drop table if exists `student`; CREATE TABLE `student` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '學生 id', `name` varchar(50) DEFAULT NULL COMMENT '學生姓名', `tid` int DEFAULT NULL COMMENT '學生所屬老師 id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; insert into student value (1,'jojo',1); insert into student value (2,'dio',1); insert into student value (3,'faro',2); insert into student value (4,'kkk',2); insert into student value (5,'ttt',3); # 老師表 drop table if exists `teacher`; CREATE TABLE `teacher` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '老師 id', `name` varchar(50) DEFAULT NULL COMMENT '老師姓名', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; insert into teacher value (1,'老師1'); insert into teacher value (2,'老師2'); insert into teacher value (3,'老師3');
學生:
public class Student { private int id; private String name; private Teacher teacher; }
老師:
public class Teacher { private int id; private String name; }
1)、按照查詢嵌套處理
接口:
List<Student> getAll();
.xml
實現(xiàn)
相當于在 resultMap 中,再執(zhí)行一次查詢
<select id="getAll" resultMap="stusta"> select * from student </select> <resultMap id="stusta" type="Student" > <id property="id" column="id"/> <result property="name" column="name"/> <result property="tid" column="tid"/> <!--這里的tid的值,會傳遞到 getTeacher 中,從而查詢出對應(yīng)教師--> <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/> </resultMap> <select id="getTeacher" resultType="Teacher"> select * from teacher where id = #{id} </select>
測試:
@Test public void getAllTest() { SqlSession sqlSession = MybatisUtil.getSqlSession(); /** * 使用動態(tài)代理的方式,生成 mapper 的實現(xiàn)類 */ StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List<Student> all = mapper.getAll(); for (Student student : all) { System.out.println(student); } sqlSession.close(); }
2)、按照結(jié)果嵌套處理
這種方法類似于 聯(lián)表查詢
<!--方法2:按照結(jié)果嵌套處理(這種方式更好懂)--> <select id="getStudentList" resultMap="StudentTeacher"> select s.id sid,s.name sname,t.name tname,t.id tid from student s,teacher t where s.tid=t.id </select> <resultMap id="StudentTeacher" type="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <association property="teacher" javaType="Teacher"> <result property="id" column="tid"/> <result property="name" column="tname"/> </association> </resultMap>
按照方便程度來說,明顯是第一種方式比較方便(sql寫的少)
但是按照性能來說,是第二種性能更好(畢竟只要查詢一次數(shù)據(jù)庫)
2、對多關(guān)系
一對多的方式和多對一差不多
比如:一個老師擁有多個學生
環(huán)境搭建,和剛才一樣編寫實體類
public class Student { private int id; private String name; private int tid; }
public class Teacher { private int id; private String name; //一個老師多個學生 private List<Student> students; }
1)、按照查詢嵌套查詢
接口:
List<Teacher> getAll();
.xml
實現(xiàn)
這里的 ofType,指明的是集合中元素的泛型
<select id="getAll" resultMap="getStaStu"> select * from teacher </select> <resultMap id="getStaStu" type="Teacher"> <id column="id" property="id"/> <result column="name" property="name"/> <!--這里的id ,是老師的id ,會自動映射到子查詢的中的 tid 中--> <collection property="students" column="id" javaType="ArrayList" ofType="Student" select="selectStudentByTid"/> </resultMap> <select id="selectStudentByTid" resultType="Student"> select * from student where tid =#{tid} </select>
測試:
@Test public void getAllTest() { SqlSession sqlSession = MybatisUtil.getSqlSession(); /** * 使用動態(tài)代理的方式,生成 mapper 的實現(xiàn)類 */ TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); List<Teacher> all = mapper.getAll(); for (Teacher teacher : all) { System.out.println(teacher); } sqlSession.close(); }
2)、按照結(jié)果嵌套查詢
接口:
List<Teacher> getAll();
.xml
實現(xiàn):
<select id="getAll" resultMap="getStaStu"> select teacher.id tid,teacher.name tname,student.id sid,student.name sname from teacher,student where teacher.id=student.tid; </select> <resultMap id="getStaStu" type="Teacher"> <id column="tid" property="id"/> <result column="tname" property="name"/> <collection property="students" javaType="ArrayList" ofType="Student"> <result column="sid" property="id"/> <result column="sname" property="name"/> </collection> </resultMap>
測試:
@Test public void getAllTest() { SqlSession sqlSession = MybatisUtil.getSqlSession(); /** * 使用動態(tài)代理的方式,生成 mapper 的實現(xiàn)類 */ TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); List<Teacher> all = mapper.getAll(); for (Teacher teacher : all) { System.out.println(teacher); } sqlSession.close(); }
十、動態(tài) SQL
使用動態(tài) SQL ,避免了 SQL 拼接的煩惱
where標簽在select中的使用
注意: 在 mybatis 的 xml 實現(xiàn)中 >可以直接使用,但是 <不可以直接使用,必須使用轉(zhuǎn)移符號
<select id="queryByVO" parameterType="QueryVO" resultMap="baseResultMap"> select * from team <where> <!-- 如果用戶輸入了名稱,就模糊查詢 and teamName like '%?%'--> <if test="name!=null "> and teamName like concat(concat('%',#{name}),'%') </if> <if test="beginTime!=null "> and createTime>=#{beginTime} </if> <if test="endTime!=null "> and createTime<=#{endTime} </if> <if test="location!=null "> and location=#{location} </if> </where> </select>
模糊查詢:
使用模糊查詢的時候,一定要使用 concat 函數(shù),將字符串進行拼接,不能使用 + 號
<select id="getByName" resultType="top.faroz.pojo.Teacher" parameterType="string"> select * from teacher <where> <if test="name!=null"> -- 使用 concat 函數(shù),將多個字符串進行拼接 and name like concat(concat('%',#{name}),'%') </if> </where> </select>
set標簽在update中的使用
<update id="update1" parameterType="com.kkb.pojo.Team"> update team <set> <if test="teamName!=null"> teamName=#{teamName}, </if> <if test="location!=null"> location=#{location}, </if> <if test="createTime!=null"> createTime=#{createTime}, </if> </set> where teamId=#{teamId} </update>
forEach標簽
批量添加
<insert id="addList" parameterType="arraylist"> INSERT INTO team (teamName,location) VALUES <!--collection:要遍歷的集合;參數(shù)是集合類型,直接寫list item:遍歷的集合中的每一個數(shù)據(jù) separator:將遍歷的結(jié)果用,分割--> <foreach collection="list" item="t" separator=","> (#{t.teamName},#{t.location}) </foreach> </insert>
批量刪除
<delete id="delList" > delete from team where teamId in <!--collection:要遍歷的集合;參數(shù)是集合類型,直接寫list item:遍歷的集合中的每一個數(shù)據(jù)separator:將遍歷的結(jié)果用,分割 open="(" close=")":表示將遍歷結(jié)果用open close包裹起來--> <foreach collection="list" item="teamId" separator="," open="(" close=")"> #{teamId} </foreach> </delete>
SQL
片段
提取SQL片段:
<sql id="if-title-author"> <if test="title != null"> title = #{title} </if> <if test="author != null"> and author = #{author} </if> </sql>
引用SQL片段:
<select id="queryBlogIf" parameterType="map" resultType="blog"> select * from blog <where> <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace --> <include refid="if-title-author"></include> <!-- 在這里還可以引用其他的 sql 片段 --> </where> </select>
注意:
①、最好基于 單表來定義 sql 片段,提高片段的可重用性
②、在 sql 片段中不要包括 where
十一、分頁插件
分頁插件,我們使用 pageHelper
1、快速開始
1)、Maven 依賴
<!--pagehelper--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.10</version> </dependency>
2)、Mybatis全局配置文件中添加插件配置
<plugins> <!-- 引入 pageHelper插件 --> <!--注意這里要寫成PageInterceptor, 5.0之前的版本都是寫PageHelper, 5.0之后要換成PageInterceptor--> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!--reasonable:分頁合理化參數(shù),默認值為false,直接根據(jù)參數(shù)進行查詢。當該參數(shù)設(shè)置為 true 時,pageNum<=0 時會查詢第一頁, pageNum>pages(超過總數(shù)時),會查詢最后一頁。 方言可以省略,會根據(jù)連接數(shù)據(jù)的參數(shù)url自動推斷--> <!--<property name="reasonable" value="true"/>--> </plugin> </plugins>
3)、使用插件
2、PageHelper 介紹
PageHelper 會攔截查詢語句,然后添加分頁語句
@Test public void getAllTest() { SqlSession sqlSession = MybatisUtil.getSqlSession(); /** * 使用動態(tài)代理的方式,生成 mapper 的實現(xiàn)類 */ TeamMapper mapper = sqlSession.getMapper(TeamMapper.class); /** * 查詢頁數(shù)為1,查詢5條 */ PageHelper.startPage(2,5); List<Team> all = mapper.getAll(); for (Team team : all) { System.out.println(team); } sqlSession.close(); }
3、PageInfo 介紹
可以用來打印分頁的相關(guān)信息,如總條數(shù),當前頁數(shù),總頁數(shù)等
@Test public void getAllTest() { SqlSession sqlSession = MybatisUtil.getSqlSession(); /** * 使用動態(tài)代理的方式,生成 mapper 的實現(xiàn)類 */ TeamMapper mapper = sqlSession.getMapper(TeamMapper.class); /** * 查詢頁數(shù)為1,查詢5條 */ PageHelper.startPage(2,5); List<Team> all = mapper.getAll(); for (Team team : all) { System.out.println(team); } PageInfo<Team> pageInfo = new PageInfo<>(all); System.out.println("分頁信息如下:"); System.out.println("總條數(shù):"+pageInfo.getTotal()); System.out.println("總頁數(shù):"+pageInfo.getPages()); System.out.println("當前頁數(shù):"+pageInfo.getPageNum()); System.out.println("每頁條數(shù):"+pageInfo.getPageSize()); sqlSession.close(); }
十二、緩存
通過使用緩存,可以在第一次查詢的時候,將查詢到的信息,放入緩存,這樣,在第二次查詢的時候,就會走緩存,從而,減輕數(shù)據(jù)庫壓力、提高查詢效率,解決高并發(fā)問題。
MyBatis 也有一級緩存和二級緩存,并且預(yù)留了集成第三方緩存的接口。
1、一級緩存
自動開啟,SqlSession級別的緩存
在操作數(shù)據(jù)庫時需要構(gòu)造 sqlSession對象,在對象中有一個(內(nèi)存區(qū)域)數(shù)據(jù)結(jié)構(gòu)(HashMap)用于存儲緩存數(shù)據(jù)。不同的sqlSession之間的緩存數(shù)據(jù)區(qū)域(HashMap)是互相不影響的。
一級緩存的作用域是同一個SqlSession,在同一個sqlSession中兩次執(zhí)行相同的sql語句,第一次執(zhí)行完畢會將數(shù)據(jù)庫中查詢的數(shù)據(jù)寫到緩存(內(nèi)存),第二次會從緩存中獲取數(shù) 據(jù)將不再從數(shù)據(jù)庫查詢,從而提高查詢效率。
當一個sqlSession結(jié)束后該sqlSession中的一級緩存也就不存在了。 Mybatis默認開啟一級緩存,存在內(nèi)存中(本地緩存)不能被關(guān)閉,可以調(diào)用clearCache()來清空本地緩存,或者改變緩存的作用域。
1)、一級緩存介紹
當用戶發(fā)起第一次查詢team=1001的時候,先去緩存中查找是否有team=1001的對象;如果沒有,繼續(xù)向數(shù)據(jù)中發(fā)送查詢語句,查詢成功之后會將teamId=1001的結(jié)果存入緩存 中;
當用戶發(fā)起第2次查詢team=1001的時候,先去緩存中查找是否有team=1001的對象,因為第一次查詢成功之后已經(jīng)存儲到緩存中,此時可以直接從緩存中獲取到該數(shù)據(jù),意味 著不需要再去向數(shù)據(jù)庫發(fā)送查詢語句。
如果SqlSession執(zhí)行了commit(有增刪改的操作),此時該SqlSession對應(yīng)的緩存區(qū)域被整個清空,目的避免臟讀。
**前提:**SqlSession未關(guān)閉。
2)、清空緩存的方式
1s、 session.clearCache( ) ; 2、 execute update(增刪改) ; 3、 session.close( ); 4、 xml配置 flushCache="true" ; 5、 rollback; 6、 commit。
2、二級緩存
Mapper 級別的緩存,可以跨 sqlSession
1)、二級緩存介紹
二級緩存是多個SqlSession共享的,其作用域是mapper的同一個namespace。
不同的sqlSession兩次執(zhí)行相同namespace下的sql語句參數(shù)相同即最終執(zhí)行相同的sql語句,第一次執(zhí)行完畢會將數(shù)據(jù)庫中查詢的數(shù)據(jù)寫到緩存(內(nèi)存),第二次會從緩存中獲 取數(shù)據(jù)將不再從數(shù)據(jù)庫查詢,從而提高查詢效率。
Mybatis默認沒有開啟二級緩存,需要在setting全局參數(shù)中配置開啟二級緩存。 如果緩存中有數(shù)據(jù)就不用從數(shù)據(jù)庫中獲取,大大提高系統(tǒng)性能。
與一級緩存一樣,一旦反生 增刪改,并commit,就會清空緩存的數(shù)據(jù),從而避免數(shù)據(jù)臟讀
其原理圖如下:
2)、使用二級緩存
3)、二級緩存的禁用
為什么需要禁用二級緩存?
在某些情況下,有一些數(shù)據(jù)被修改的頻率是十分頻繁的,一旦開啟二級緩存,那么對其緩存清空的操作也會十分頻繁,從而增大數(shù)據(jù)庫的壓力,我們可以但為這些 sql 查詢,關(guān)閉二級緩存:
在開始了二級緩存的XML中對應(yīng)的statement中設(shè)置useCache=false禁用當前Select語句的二級緩存,意味著該SQL語句每次只需都去查詢數(shù)據(jù)庫,不會查詢緩存。 useCache默認值是true。對于一些很重要的數(shù)據(jù)盡不放在二級緩存中。
4)、緩存的屬性配置
<cache> <property name="eviction" value="LRU"/><!--回收策略為LRU--> <property name="flushInterval" value="60000"/><!--自動刷新時間間隔為60S--> <property name="size" value="1024"/><!--最多緩存1024個引用對象--> <property name="readOnly" value="true"/><!--只讀--> </cache>
源碼如下:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface CacheNamespace { Class<? extends Cache> implementation() default PerpetualCache.class; Class<? extends Cache> eviction() default LruCache.class; long flushInterval() default 0L; int size() default 1024; boolean readWrite() default true; boolean blocking() default false; Property[] properties() default {}; } /**屬性介紹: 1.映射語句文件中的所有select語句將會被緩存; 2.映射語句文件中的所有CUD操作將會刷新緩存; 3.緩存會默認使用LRU(Least Recently Used)算法來收回; 3.1、LRU – 最近最少使用的:移除最長時間不被使用的對象。 3.2、FIFO – 先進先出:按對象進入緩存的順序來移除它們。 3.3、SOFT – 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象。 3.4、WEAK – 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象。 4.緩存會根據(jù)指定的時間間隔來刷新(默認情況下沒有刷新間隔,緩存僅僅調(diào)用語句時刷新); 5.緩存會存儲列表集合或?qū)ο?無論查詢方法返回什么),默認存儲1024個對象。 6.緩存會被視為是read/write(可讀/可寫)的緩存,意味著檢索對象不是共享的,而且可以安全地被調(diào)用者修改,而不干擾其他調(diào)用者或線程所做的潛在修改。*/
如果想在命名空間中共享相同的緩存配置和實例,可以使用cache-ref 元素來引用另外一個緩存。
所謂命名空間,其實就是一個個 xml 實現(xiàn)
<cache-ref namespace="com.kkb.mapper.TeamMapper" /> //引用TeamMapper命名空間中的cache。
十三、反向生成器
使用反向生成器,就可以根據(jù)數(shù)據(jù)庫表格,去自動生成持久層的代碼
1、配置
略
2、使用
這里介紹其中的部分使用注意點
1)、動態(tài)更新/插入
帶 selective 關(guān)鍵字的,表示動態(tài)改變
//動態(tài)插入 mapper.insertSelective(User user); //動態(tài)更新 mapper.updateByPrimaryKeySelective(User user);
對于插入,是對插入對象中,屬性為空的地方,不寫上插入 sql
對于更新,是對屬性為空的部分,不執(zhí)行更新操作()即不會用新對象的空洞部分,去覆蓋元數(shù)據(jù)區(qū)
2)、多條件查詢
多條件查詢,需要用到 XxxExample
使用步驟如下:
//1、創(chuàng)建被查詢對象的 Example 對象 EbookExample ebookExample = new EbookExample(); //2、創(chuàng)建盛放查詢條件的容器 EbookExample.Criteria criteria = ebookExample.createCriteria(); //3、在容器中,防止多查詢條件 criteria.andNameLike("spring");//模糊查詢 criteria.andNameEqualTo("spr");//等于 //...還有很多,對于每個屬性,都有等量的多查詢條件可供選擇 //4、傳入 Example 對象,進行多條件查詢 mapper.selectByExample(ebookExample);
十四.總結(jié)
以上就是本文針對MyBatis入門學習教程-MyBatis快速入門的全部內(nèi)容,希望大家多多關(guān)注腳本之家的其他內(nèi)容!
相關(guān)文章
Feign如何解決服務(wù)之間調(diào)用傳遞token
這篇文章主要介紹了Feign如何解決服務(wù)之間調(diào)用傳遞token,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03Java泛型T,E,K,V,N,?與Object區(qū)別和含義
Java?泛型(generics)是?JDK?5?中引入的一個新特性,?泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。本文將詳細講講Java泛型T、E、K、V、N、?和Object區(qū)別和含義,需要發(fā)可以參考一下2022-03-03基于java查找并打印輸出字符串中字符出現(xiàn)次數(shù)
這篇文章主要介紹了基于java查找并打印輸出字符串中字符出現(xiàn)次數(shù),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-11-11windows如何使用bat腳本后臺啟動/停止和重啟jar包服務(wù)
這篇文章主要介紹了windows使用bat腳本后臺啟動/停止和重啟jar包服務(wù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-115種必會的Java異步調(diào)用轉(zhuǎn)同步的方法你會幾種
這篇文章主要介紹了5種必會的Java異步調(diào)用轉(zhuǎn)同步的方法你會幾種,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12