Executor簡(jiǎn)介
Executor
Executor是MyBatis的核心接口之一,其中定義了數(shù)據(jù)庫(kù)操作的基本方法。在實(shí)際應(yīng)用中經(jīng)常涉及的SqlSession接口的功能,都是基于Executor接口實(shí)現(xiàn)的。
BaseExecutor
BaseExecutor是一個(gè)實(shí)現(xiàn)了Executor接口的抽象類(lèi),它實(shí)現(xiàn)了Executor 接口的大部分方法。BaseExecutor 中主要提供了緩存管理和事務(wù)管理的基本功能。繼承BaseExecutor 的子類(lèi)只要實(shí)現(xiàn)四個(gè)基本方法來(lái)完成數(shù)據(jù)庫(kù)的相關(guān)操作即可,這四個(gè)方法是:doUpdate()方法、doQuery()方法、doQueryCursor()方法、doFlushstatement()方法, 其余的功能在BaseExecutor中實(shí)現(xiàn)。
SimpleExecutor
SimpleExecutor 繼承了BaseExecutor 抽象類(lèi),它是最簡(jiǎn)單的 Executor 接口實(shí)現(xiàn)。Executor使用了模板方法模式,一級(jí)緩存等固定不變的操作都封裝到了BaseExecutor中,SimpleExecutor中就不必再關(guān)心一級(jí)緩存等操作,只需要專(zhuān)注實(shí)現(xiàn)4個(gè)基本方法的實(shí)現(xiàn)即可。
ReuseExecutor
在傳統(tǒng)的JDBC編程中,重用Statement對(duì)象是常用的一種優(yōu)化手段,該優(yōu)化手段可以減少SQL預(yù)編譯的開(kāi)銷(xiāo)以及創(chuàng)建和銷(xiāo)毀 Statement 對(duì)象的開(kāi)銷(xiāo),從而提高性能。
ReuseExecutor提供了Statement重用的功能,ReuseExecutor 中通過(guò)statementMap字段(HashMap<String, Statement>類(lèi)型)緩存使用過(guò)的 Statement 對(duì)象,key是SQL 語(yǔ)句,value是SQL對(duì)應(yīng)的Statement對(duì)象。
ReuseExecutor.doQuery()、doQueryCursor()、doUpdate()方法的實(shí)現(xiàn)與SimpleExecutor 中對(duì)應(yīng)方法的實(shí)現(xiàn)一樣,區(qū)別在于其中調(diào)用的 prepareStatement()方法,SimpleExecutor 每次都會(huì)通過(guò)JDBC Connection創(chuàng)建新的Statement對(duì)象,而ReuseExecutor則會(huì)先嘗試重用StatementMap中緩存的Statement對(duì)象。
BatchExecutor
應(yīng)用系統(tǒng)在執(zhí)行一條SQL語(yǔ)句時(shí),會(huì)將SQL語(yǔ)句以及相關(guān)參數(shù)通過(guò)網(wǎng)絡(luò)發(fā)送到數(shù)據(jù)庫(kù)系統(tǒng)。對(duì)于頻繁操作數(shù)據(jù)庫(kù)的應(yīng)用系統(tǒng)來(lái)說(shuō),如果執(zhí)行一條SQL語(yǔ)句就向數(shù)據(jù)庫(kù)發(fā)送一次請(qǐng)求,很多時(shí)間會(huì)浪費(fèi)在網(wǎng)絡(luò)通信上。使用批量處理的優(yōu)化方式可以在客戶(hù)端緩存多條SQL語(yǔ)句,并在合適的時(shí)機(jī)將多條SQL語(yǔ)句打包發(fā)送給數(shù)據(jù)庫(kù)執(zhí)行,從而減少網(wǎng)絡(luò)方面的開(kāi)銷(xiāo),提升系統(tǒng)的性能。
不過(guò)有一點(diǎn)需要注意,在批量執(zhí)行多條SQL語(yǔ)句時(shí),每次向數(shù)據(jù)庫(kù)發(fā)送的SQL語(yǔ)句條數(shù)是有上限的,如果超過(guò)這個(gè)上限,數(shù)據(jù)庫(kù)會(huì)拒絕執(zhí)行這些SQL語(yǔ)句并拋出異常。所以批量發(fā)送SQL語(yǔ)句的時(shí)機(jī)很重要。
執(zhí)行器功能演示
代碼準(zhǔn)備
創(chuàng)建配置文件mybatis-config.xml
<?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>
<properties>
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true" />
<property name="username" value="root" />
<property name="password" value="123456" />
</properties>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<environments default="default">
<environment id="default">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/EmployeeMapper.xml" />
</mappers>
</configuration>
創(chuàng)建實(shí)體類(lèi)EmployeeDO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmployeeDO {
private Integer id;
private String name;
private Integer age;
private String phone;
}
創(chuàng)建EmployeeMapper接口
public interface EmployeeMapper {
EmployeeDO getEmployeeById(Integer id);
int addEmployee(EmployeeDO employeeDO);
}
創(chuàng)建EmployeeMapper.xml
<?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">
<mapper namespace="com.ys.mybatis.mapper.EmployeeMapper">
<select id="getEmployeeById" resultType="com.ys.mybatis.DO.EmployeeDO">
select * from employee where id = #{id}
</select>
<insert id="addEmployee" useGeneratedKeys="true" keyProperty="id">
insert into employee(`name`, `age`, `phone`) VALUES (#{name},#{age},#{phone})
</insert>
</mapper>
創(chuàng)建測(cè)試類(lèi)EmployeeTest
@Slf4j
public class EmployeeTest1 {
private SqlSessionFactory sqlSessionFactory;
private Configuration configuration;
@BeforeEach
public void before() {
InputStream inputStream = ConfigurationTest.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
configuration = sqlSessionFactory.getConfiguration();
}
@Test
public void queryForSimpleExecutor() {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
EmployeeDO firstQuery = employeeMapper.getEmployeeById(1);
EmployeeDO secondQuery = employeeMapper.getEmployeeById(2);
System.out.println(firstQuery);
System.out.println(secondQuery);
}
@Test
public void queryForReuseExecutor() {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE);
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
EmployeeDO firstQuery = employeeMapper.getEmployeeById(1);
EmployeeDO secondQuery = employeeMapper.getEmployeeById(2);
System.out.println(firstQuery);
System.out.println(secondQuery);
}
@Test
public void batchProcessingForSimpleExecutor() {
Random random = new Random();
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
EmployeeDO employee = new EmployeeDO(null, getRandomName(), random.nextInt(100), getRandomPhone());
employeeMapper.addEmployee(employee);
}
sqlSession.commit();
long end = System.currentTimeMillis();
System.out.println("共耗時(shí):" + (end - start) / 1000);
}
@Test
public void batchProcessingForReuseExecutor() {
Random random = new Random();
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE);
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
EmployeeDO employee = new EmployeeDO(null, getRandomName(), random.nextInt(100), getRandomPhone());
employeeMapper.addEmployee(employee);
}
sqlSession.commit();
long end = System.currentTimeMillis();
System.out.println("共耗時(shí):" + (end - start) / 1000);
}
@Test
public void batchProcessingForBatchExecutor() {
boolean autoCommit = false;
Environment environment = configuration.getEnvironment();
Transaction transaction = environment.getTransactionFactory().newTransaction(environment.getDataSource(), TransactionIsolationLevel.REPEATABLE_READ, autoCommit);
BatchExecutor batchExecutor = new BatchExecutor(configuration, transaction);
Random random = new Random();
try (DefaultSqlSession sqlSession = new DefaultSqlSession(configuration, batchExecutor, autoCommit)) {
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
EmployeeDO employee = new EmployeeDO(null, getRandomName(), random.nextInt(100), getRandomPhone());
employeeMapper.addEmployee(employee);
}
sqlSession.commit();
long end = System.currentTimeMillis();
System.out.println("共耗時(shí):" + (end - start) / 1000);
}
}
private String getRandomName() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 5);
}
private String getRandomPhone() {
StringBuilder sb = new StringBuilder("1");
Random random = new Random();
IntStream.range(0, 10).forEach(i -> sb.append(random.nextInt(10)));
return sb.toString();
}
}
查詢(xún) : SimpleExecutor vs?ReuseExecutor
執(zhí)行queryForSimpleExecutor
每執(zhí)行一次查詢(xún),預(yù)編譯一次
執(zhí)行queryForReuseExecutor
多次查詢(xún),只預(yù)編譯一次
批量插入 : SimpleExecutor vs?ReuseExecutor vs?BatchExecutor
執(zhí)行batchProcessingForSimpleExecutor
插入1000條數(shù)據(jù)耗時(shí) : 55s
執(zhí)行batchProcessingForReuseExecutor
插入1000條數(shù)據(jù)耗時(shí) : 47s
執(zhí)行batchProcessingForBatchExecutor
插入1000條數(shù)據(jù)耗時(shí) : 21s
原因簡(jiǎn)析
- ReuseExecutor? vs SimpleExecutor : 減少了預(yù)編譯次數(shù)
- BatchExecutor vs?ReuseExecutor? : 減少了與數(shù)據(jù)庫(kù)交互次數(shù)
PS :?ReuseExecutor和SimpleExecutor在執(zhí)行批量插入的時(shí)候其實(shí)性能差距不太大,要是執(zhí)行ReuseExecutor的批量插入時(shí)剛好機(jī)器負(fù)載大,執(zhí)行SimpleExecutor的批量插入時(shí)機(jī)器負(fù)載小,有可能出現(xiàn)SimpleExecutor的批量插入用時(shí)更少的情況
源碼簡(jiǎn)析
SimpleExecutor.doQuery() vs?ReuseExecutor.doQuery()
SimpleExecutor的doQuery()方法和ReuseExecutor的doQuery()方法基本上是一致的,主要差異就是prepareStatement這個(gè)方法的內(nèi)部實(shí)現(xiàn)。SimpleExecutor每次都重新編譯Statement,而ReuseExecutor則是將Statement緩存起來(lái),以供下次查詢(xún)使用
SimpleExecutor.doQuery() vs?BatchExecutor.doQuery()
如果僅執(zhí)行查詢(xún)操作,SimpleExecutor的doQuery()方法和BatchExecutor的doQuery()方法的效果是一樣的,只是BatchExecutor的doQuery()方法會(huì)多一個(gè)批量刷新的方法。BatchExecutor執(zhí)行器執(zhí)行插入或者更新操作的時(shí)候不會(huì)立即執(zhí)行,而是封裝成BatchResult對(duì)象,等執(zhí)行flushStatements方法的時(shí)候才是真正執(zhí)行相關(guān)操作。即BatchExecutor在執(zhí)行更新操作中間執(zhí)行了查詢(xún)操作也會(huì)觸發(fā)批處理,如果更新操作中間沒(méi)有查詢(xún)操作,那只有等到執(zhí)行sqlSession的commit方法才會(huì)執(zhí)行flushStatements
BatchExecutor的更新操作
BatchExecutor會(huì)拿本次執(zhí)行的sql和MappedStatement與上一次的對(duì)比,如果是一致的,則從緩存中獲取上一次使用的Statement,減少了預(yù)編譯次數(shù),這一點(diǎn)類(lèi)似ReuseExecutor的查詢(xún)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-852873.html
PS : ReuseExecutor的doQuery是根據(jù)sql查找緩存,BatchExecutor的doUpdate是根據(jù)sql和MappedStatement查找緩存。個(gè)人理解:不同Statement的timeout和fetchSize可能不同,而更新操作是比較重要的,所以不能混用文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-852873.html
到了這里,關(guān)于Mybatis執(zhí)行器(Executor)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!