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