Mybatis批量操作与JDBC批量操作效率测试

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis批量操作与JDBC批量操作效率测试相关的知识,希望对你有一定的参考价值。

Mybatis-spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
	
	<!-- datasource -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
		init-method="init" destroy-method="close">
		<property name="url" value="${admin.datasource.url}" />
		<property name="username" value="${admin.datasource.username}" />
		<property name="password" value="${datasource.password}" />
		<property name="maxActive" value="${admin.datasource.maxActive}" />
		<property name="maxWait" value="${admin.datasource.maxWait}" />
		<property name="initialSize" value="1" />
		<property name="minIdle" value="1" />
		<property name="timeBetweenEvictionRunsMillis" value="3000" />
		<property name="minEvictableIdleTimeMillis" value="300000" />
		<property name="validationQuery" value="SELECT ‘x‘" />
		<property name="testWhileIdle" value="true" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />
		<!-- <property name="poolPreparedStatements" value="false" /> -->
		<property name="maxPoolPreparedStatementPerConnectionSize"
			value="500" />
	</bean>
	<!-- sqlSessionFactory,mybatis -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<!-- 生成的所有Mapper -->
		<property name="configLocation" value="classpath:application-entity.xml" />
		<!-- 自定义Mapper.xml -->
		<property name="mapperLocations">
			<list>
				<value>classpath:batchtest/*.xml</value>
			</list>
		</property>
		<!-- 扫描model包 -->
		<property name="typeAliasesPackage"
			value="com.momix.dbao.dao.entity,com.momix.heda.model.entity" />
		<property name="plugins"><!--分页插件 -->
			<array>
				<bean class="com.github.pagehelper.PageHelper">
					<property name="properties">
						<value>
							dialect=mysql
							reasonable=true
						</value>
					</property>
				</bean>
			</array>
		</property>
	</bean>

	<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSessionFactory" />
	</bean>
	
	<!-- scann mapper -->
	<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.momix.dbao.dao.mapper,com.momix.heda.dao" />
		<property name="properties">
			<value>
				mappers=com.momix.dbao.dao.core.MyBatisBaseMapper
			</value>
		</property>
	</bean>
	
	<!-- transaction -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="get*" propagation="SUPPORTS" />
			<tx:method name="find*" propagation="SUPPORTS" />
			<tx:method name="create*" propagation="REQUIRED" read-only="false"
				rollback-for="java.lang.Exception" />
			<tx:method name="del*" propagation="REQUIRED" read-only="false"
				rollback-for="java.lang.Exception" />
			<tx:method name="update*" propagation="REQUIRED" read-only="false"
				rollback-for="java.lang.Exception" />
			<tx:method name="*" propagation="REQUIRED" read-only="false"
				rollback-for="java.lang.Exception" />
		</tx:attributes>
	</tx:advice>
	<aop:config proxy-target-class="true" expose-proxy="true">
		<aop:pointcut id="dbao"
			expression="execution(* com.momix.dbao.service..*(..))" />
		<aop:advisor pointcut-ref="dbao" advice-ref="txAdvice" />
	</aop:config>

	<!-- scan service -->
	<context:component-scan base-package="com.momix.dbao.service,com.momix.heda.service,com.momix.basedao,com.momix.dbao.dao.sqlxml">
		<context:exclude-filter type="annotation"
			expression="org.springframework.stereotype.Controller" />
	</context:component-scan>
</beans>


Mybatis3批量插入


配置文件Sql语句:

<?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.momix.dbao.dao.mapper.sqlxml.BatchTestMapper">
	 
	 <!-- 批量新增Sql -->
	 <insert id="batchInsert" parameterType="java.util.List">
	 	insert into batch_test (name,sex,birthday,mobile) values
	 	<foreach collection="list" item="item" index="index" separator="," >  
         	(#{item.name},#{item.sex},#{item.birthday},#{item.mobile}) 
   		</foreach>
	 </insert>
	 
	 <!-- 批量修改Sql -->
	 <update id="batchUpdate" parameterType="java.util.List">
	 	<foreach collection="list" item="item" index="index" open="" close="" separator=";">
			update batch_test set 
		 		name = #{item.name},sex = #{item.sex},birthday = #{item.birthday},mobile = #{item.mobile}
		 	where id = #{item.id}
	   </foreach>
	 </update>
	 
	 <select id="getCount" resultType="Integer">
	 	select count(*) from batch_test
	 </select>
	 
	 <delete id="deleteData">
	 	delete from batch_test
	 </delete>
</mapper>

Mapper方法:

package com.momix.dbao.dao.mapper.sqlxml;

import java.util.List;

import com.momix.dbao.dao.core.MyBatisBaseMapper;
import com.momix.dbao.dao.entity.sqlxml.BatchTest;

public interface BatchTestMapper extends MyBatisBaseMapper<BatchTest> {

	/**
	 * 批量插入方法
	 * 
	 * @param params
	 *            插入集合
	 */
	void batchInsert(List<BatchTest> params);

	/**
	 * 批量修改方法
	 * 
	 * @param params
	 *            修改集合
	 */
	void batchUpdate(List<BatchTest> params);

	/**
	 * 删除数据
	 * 
	 * @return 删除行数
	 */
	int deleteData();

	/**
	 * 获取数据库所有行数
	 */
	int getCount();

}


测试代码:

采用TestNG进行测试

package com.momix.test.batch;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import com.momix.dbao.dao.dto.sqlxml.BatchTestDTO;
import com.momix.dbao.dao.entity.sqlxml.BatchTest;
import com.momix.dbao.dao.mapper.sqlxml.BatchTestMapper;
import com.momix.dbao.service.test.TestOneService;

@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class BatchTestMain extends AbstractTestNGSpringContextTests {

	@Autowired
	private TestOneService testOneService;

	@Autowired
	private BatchTestMapper batchTestMapper;

	private List<BatchTest> paramsMapper;

	// 每次测试插入行数
	int size = 0;
	// 所有测试案例
	List<Integer> sizes = new ArrayList<Integer>();

	@BeforeTest
	public void initData() {
		sizes.add(100);
		sizes.add(300);
		sizes.add(500);
		sizes.add(800);
		sizes.add(1000);
		sizes.add(1500);
		sizes.add(2000);
		sizes.add(3000);
		sizes.add(5000);
		sizes.add(8000);
		sizes.add(10000);
		sizes.add(30000);
		sizes.add(50000);
		sizes.add(100000);
	}


	public void printData(List<Map<String, Long>> infos) {
		System.out
				.println("***********************************************************************");
		for (int i = 0; i < infos.size(); i++) {
			Map<String, Long> info = infos.get(i);
			System.out.println("测试插入行数 : " + info.get("a") + "   实际插入行数: "
					+ info.get("b") + "  删除行数 : " + info.get("d") + "  耗时 : "
					+ info.get("c"));
		}
		System.out
				.println("***********************************************************************");
	}

	@Test(enabled = true)
	public void testBatchInsertMapper() {
		List<Map<String, Long>> infos = new ArrayList<Map<String, Long>>();
		try {
			long start = 0l;
			long end = 0l;
			int b = 0;
			int d = 0;
			Map<String, Long> info = null;
			for (int i = 0; i < sizes.size(); i++) {
				info = new HashMap<String, Long>();
				// 当前插入行数
				size = sizes.get(i);
				// 当前测试插入数据
				paramsMapper = this.getDataMapper();
				System.out.println(paramsMapper.size());
				// 开始时间
				start = System.currentTimeMillis();
				// 批量插入开始
				testOneService.batchTestInsertMapper(paramsMapper);
				// 结束时间
				end = System.currentTimeMillis();

				b = batchTestMapper.getCount();

				d = batchTestMapper.deleteData();

				info.put("a", (long) size); // 应当插入行数
				info.put("b", (long) b);// 实际插入行数
				info.put("c", (end - start)); // 耗时
				info.put("d", (long) d); // 删除行数

				infos.add(info);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
		
		printData(infos);
	}

	private List<BatchTest> getDataMapper() {
		List<BatchTest> tests = new ArrayList<BatchTest>();

		BatchTest test = null;
		for (int i = 0; i < size; i++) {
			test = new BatchTest();
			test.setName(String.valueOf(i));
			test.setSex(0);
			tests.add(test);
		}

		return tests;
	}

}


通过org.mybatis.spring.SqlSessionTemplate进行批量操作,分步提交


Sql配置文件

<?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.momix.dbao.dao.dto.sqlxml.BathcTest">
	 
	 <!-- 批量新增Sql -->
	 <insert id="batchInsertBatchTest" useGeneratedKeys="true" keyProperty="id" parameterType="com.momix.dbao.dao.dto.sqlxml.BatchTestDTO">
	 	insert into batch_test (name,sex,birthday,mobile)
	 		values (#{name},#{sex},#{birthday},#{mobile})
	 </insert>
	 
	 <!-- 批量修改Sql -->
	 <insert id="batchUpdateBatchTest" parameterType="com.momix.dbao.dao.dto.sqlxml.BatchTestDTO">
	 	update batch_test set 
	 		name = #{name},sex = #{sex},birthday = #{birthday},mobile = #{mobile}
	 	where id = #{id}
	 </insert>
	 
</mapper>


底层代码:

BaseDao代码

package com.momix.basedao;

import java.util.List;

import javax.annotation.Resource;

import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;

public abstract class BaseDao<O> {

	@Resource
	private SqlSessionTemplate sqlSessionTemplate;
	
	/**
	 * 获取当前xml的namespace
	 * @return String
	 */
	protected abstract String getNameSpace();

	/**
	 * 批量修改 分步提交
	 * 
	 * @param statementName
	 *            ID
	 * @param data
	 *            数据集合
	 */
	protected void batchUpdate(String statementName, List<O> data) {
		// 当前方法没有用Spring事务
		SqlSession session = sqlSessionTemplate.getSqlSessionFactory()
				.openSession();
		try {
			int size = data.size();
			for (int i = 0; i < size; i++) {
				O param = data.get(i);
				session.update(getNameSpace() + "." + statementName, param);
				if (size % 100 == 0) {
					// 每200条提交
					session.commit();
					// 清楚缓存,防止溢出
					session.clearCache();
				}
			}
		} catch (Exception e) {
			// 出现异常,回滚还没有提交的数据
			session.rollback();
			e.printStackTrace();
		} finally {
			session.close();
		}

	}

	/**
	 * 批量插入 分步提交
	 * 
	 * @param statementName
	 *            ID
	 * @param data
	 *            数据集合
	 */
	protected void batchInsert(String statementName, List<O> data) {
		// 当前方法没有用Spring事务
		SqlSession session = sqlSessionTemplate.getSqlSessionFactory()
				.openSession();
		try {
			int size = data.size();
			for (int i = 0; i < size; i++) {
				O param = data.get(i);
				session.update(getNameSpace() + "." + statementName, param);
				if (size % 100 == 0) {
					// 每200条提交
					session.commit();
					// 清楚缓存,防止溢出
					session.clearCache();
				}
			}
		} catch (Exception e) {
			// 出现异常,回滚还没有提交的数据
			session.rollback();
			e.printStackTrace();
		} finally {
			session.close();
		}
	}
}


Dao代码:

package com.momix.dbao.dao.sqlxml;

import java.util.List;

import org.springframework.stereotype.Repository;

import com.momix.basedao.BaseDao;
import com.momix.dbao.dao.dto.sqlxml.BatchTestDTO;

@Repository
public class BatchTestDao extends BaseDao<BatchTestDTO> {

	@Override
	protected String getNameSpace() {
		return "com.momix.dbao.dao.dto.sqlxml.BathcTest";
	}

	public void batchInsert(List<BatchTestDTO> params) {
		super.batchInsert("batchInsertBatchTest", params);
	}

	public void batchUpdate(List<BatchTestDTO> params) {
		super.batchUpdate("batchUpdateBatchTest", params);
	}

}


测试代码:

package com.momix.test.batch;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import com.momix.dbao.dao.dto.sqlxml.BatchTestDTO;
import com.momix.dbao.dao.entity.sqlxml.BatchTest;
import com.momix.dbao.dao.mapper.sqlxml.BatchTestMapper;
import com.momix.dbao.service.test.TestOneService;

@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class BatchTestMain extends AbstractTestNGSpringContextTests {

	@Autowired
	private TestOneService testOneService;

	@Autowired
	private BatchTestMapper batchTestMapper;

	private List<BatchTestDTO> paramsDao;

	// 每次测试插入行数
	int size = 0;
	// 所有测试案例
	List<Integer> sizes = new ArrayList<Integer>();

	@BeforeTest
	public void initData() {
		sizes.add(100);
		sizes.add(300);
		sizes.add(500);
		sizes.add(800);
		sizes.add(1000);
		sizes.add(1500);
		sizes.add(2000);
		sizes.add(3000);
		sizes.add(5000);
		sizes.add(8000);
		sizes.add(10000);
		sizes.add(30000);
		sizes.add(50000);
		sizes.add(100000);
	}

	@Test(enabled = false)
	public void testBatchInsertDao() {
		long start = 0l;
		long end = 0l;
		int b = 0;
		int d = 0;
		List<Map<String, Long>> infos = new ArrayList<Map<String, Long>>();
		Map<String, Long> info = null;
		for (int i = 0; i < sizes.size(); i++) {
			info = new HashMap<String, Long>();
			// 当前插入行数
			size = sizes.get(i);
			// 当前测试插入数据
			paramsDao = this.getDataDao();
			// 开始时间
			start = System.currentTimeMillis();
			// 批量插入开始
			testOneService.batchTestInsertDao(paramsDao);
			// 结束时间
			end = System.currentTimeMillis();

			b = batchTestMapper.getCount();

			d = batchTestMapper.deleteData();

			info.put("a", (long) size); // 应当插入行数
			info.put("b", (long) b);// 实际插入行数
			info.put("c", (end - start)); // 耗时
			info.put("d", (long) d); // 删除行数

			infos.add(info);
		}

		printData(infos);
	}

	public void printData(List<Map<String, Long>> infos) {
		System.out
				.println("***********************************************************************");
		for (int i = 0; i < infos.size(); i++) {
			Map<String, Long> info = infos.get(i);
			System.out.println("测试插入行数 : " + info.get("a") + "   实际插入行数: "
					+ info.get("b") + "  删除行数 : " + info.get("d") + "  耗时 : "
					+ info.get("c"));
		}
		System.out
				.println("***********************************************************************");
	}

	private List<BatchTestDTO> getDataDao() {
		List<BatchTestDTO> tests = new ArrayList<BatchTestDTO>();

		BatchTestDTO test = null;
		for (int i = 0; i < size; i++) {
			test = new BatchTestDTO();
			test.setName(String.valueOf(i));
			test.setSex(0);
			tests.add(test);
		}

		return tests;
	}
}


测试结果:

技术分享

总结:

  1. 通过jdbc循环分步提交,耗时较长,但比较稳定。

  2. 通过Mybatis用foreach进行批量操作,耗时较短,但数据量过大会出现异常

  3. 选择哪种方式,只能视业务场景而定。

以上是关于Mybatis批量操作与JDBC批量操作效率测试的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis # 使用mybatis批量操作数据,提升效率

MyBatis-Plus的saveBatch批量插入为何效率很低耗时长详解及解决方案

2500-使用MyBatis操作MySQL进行批量更新的注意事项

MyBatis批量增删改的另外一种思路(推荐)

mybatis批量操作

Mybatis 批量操作-删除修改和查询