MyBatis传参方式和批量操作总结

     最近刚开发完一个项目,用了MyBatis作为数据库持久。今天分析总结一下使用的心得。

    Mybatis是一个半自动化的数据持久组件。和之前接触的Hibernate有很大的区别。Hibernate是全自动化的数据库组件,有HQL语言,使用起来很方便。但是调优的话只能在那一堆的属性里面抠了。Mybatis不提供SQL生成功能,初学者会觉得和直接拼JDBC没多大区别,只不过是对返回的数据做了一下封装罢了。而正是因为这个原因,我们的可创造性才得以体现出来。

       工程准备:

       1. Eclipse 3.7.2一个;

       2. Mybatis3.1.1 相关jar包

       3. Junit 4相关jar包

       4. 数据库连接的必要jar包。(用的是MySQL5.0.22和c3p0 0.9.0)


     精简后的jar包如下。我自己用的是IvyDE插件管理。能把包找全就可以了。

                          


     创建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>
	<settings>
		<setting name="lazyLoadingEnabled" value="false" />
	</settings>
	<typeAliases>
		<typeAlias alias="User" type="com.wheat.pojo.User" />
	</typeAliases>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
 				<property name="poolMaximumActiveConnections" value="100" />
				<property name="poolMaximumIdleConnections" value="100" />
				<property name="url" value="jdbc:mysql://localhost:3306/fitweber?characterEncoding=UTF-8" />
				<property name="username" value="root" />
				<property name="password" value="123456" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="META-INF/mappers/UserMapper.xml" />
	</mappers>
</configuration>


     在数据库建一张名为userinfo的表,表中只含两个字段,id varchar(32)userName varchar(100)。这表只是方便测试。

     重头戏是UserMapper.xml。里面包含了对表的查询、插入和删除。涉及了所有我想要总结的东西。update语句只要在insert语句上修改一下就可以了。

<?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.wheat.dao.UserDao">
<!-- 	<resultMap type="com.wheat.pojo.User" id="UserMaps"> -->
<!-- 	</resultMap> -->
	<select id="returnAllUser" resultType="java.util.HashMap" >
		<![CDATA[
			SELECT ID,USERNAME FROM USERINFO
		]]>
	</select>
	<select id="searchUserById" resultType="java.util.HashMap" parameterType="String">
		<![CDATA[
			SELECT ID,USERNAME FROM USERINFO
		]]>
		<trim prefix="where" prefixOverrides=",">
			<if test="_parameter != null">ID =#{id}</if>
		</trim>
	</select>
	<select id="searchUserByParameters" resultType="java.util.HashMap" parameterType="java.util.HashMap">
		<![CDATA[
			SELECT ID,USERNAME FROM USERINFO
		]]>
		<trim prefix="where" prefixOverrides="AND | OR">
			<if test="id != null">ID =#{id}</if>
			<if test="userName != null">AND USERNAME LIKE '%'|| #{userName} || '%'</if>
		</trim>
	</select>
	<insert id="saveUserOneByOne" parameterType="java.util.HashMap">
		<![CDATA[
			INSERT INTO USERINFO (ID,USERNAME) VALUES
		]]>
		(#{id},#{userName})
	</insert>
	<insert id="saveUsersBatch" parameterType="java.util.ArrayList">
		<![CDATA[
			INSERT INTO USERINFO (ID,USERNAME) VALUES
		]]>
			<foreach collection="list" item="item" index="index" separator=",">
			(#{item.id},#{item.userName})
			</foreach>
		<![CDATA[
		]]>
	</insert>
	<delete id="delUserBatch" parameterType="java.lang.String">
		<![CDATA[
			DELETE FROM USERINFO WHERE ID IN
		]]>
		<foreach collection="array" item="item" index="index" open="(" separator="," close=")">
			#{item}
		</foreach>
	</delete>
	<insert id="" statementType="CALLABLE">
	</insert>
</mapper>


     在这里没有使用resultMap,而是用了java.util.HashMap。使用resultMap的本意是想让Mybatis直接返回实体类对象。可是在实际开发的很多场景中,我们从数据库拿出来后也是对属性进行分拆。与其每个类都要写一次resultMap来与之对应,倒不如直接返回一个HashMap。原因在于,数据库的字段如果按照规范来设计,和实体类的中的差别不会太大。正常来说,应该实体类的创建是根据数据库来的,二者的字段是比较一致的。在返回的HashMap中可以直接通过key来取得属性的值。有一点需要注意的是,返回的key值都是大写的。如果是要在JSP上表现,EL表达式对Map的支持和实体类几乎一样。也支持Object.xxx的操作。更有意思的是,HashMap和Json其实是相通的结构,可以说是近亲。都是键值对应的设计。如果是Ajax请求Json格式返回的话。直接把HashMap通过Json-lib一转就可以直接返回了。若返回的是实体对象还要转多一道手。

        ......
	<if test="_parameter != null">ID =#{id}</if>
	......


     传进来的变量名应该叫id才对。怎么变成_parameter了呢?之前我也是id != null 这样写的。但这样会报一个Caused by:org.apache.ibatis.reflection.ReflectionException: There is no getter forproperty named 'id' in 'class java.lang.String'的错误。原因也是因为Mybatis的取参方式也是按照值对的关系来寻找的。你单单传一个String类型进来他本该新建一个key为id,value为String的Map对象。但他没有这样做,他偷懒把单个传入的值都存在了一个叫_parameter的私有变量里。所以只好这样访问了。下面一种使用HashMap来传参的方式可以避开这种情况。

	......
	parameterType="java.util.HashMap">
	......

    可以将各种类型的参数放在HashMap中传过来,可以是String、Int,也可以是List、Array。可以较为灵活地配置SQL的查询条件。取参方式和实体类一样。
    AND USERNAME LIKE '%'|| #{userName} || '%'

     上面是模糊匹配的写法。

     还要定义一个接口类供调用。类名要和Mapper文件中的namespace对应。如下:

    namespace="com.wheat.dao.UserDao"
       @SuppressWarnings("rawtypes")
       public interface UserDao {
	      public List<Map> returnAllUser();
	      public List<Map> searchUserById(String id);
	      public List<Map> searchUserByParameters(HashMap<String, String> requestParameters);
	      public void saveUserOneByOne(HashMap<String, String> user);
	      public void saveUsersBatch(ArrayList<HashMap<String, String>> users);
	      public void delUserBatch(String[] userIds);
       }

     如何让程序跑起来?这里要靠Junit了。

        @Test
	@SuppressWarnings("rawtypes")
	public void TestUserDaoWithSingleParameter() throws IOException{
		String resource = "META-INF/conf/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		SqlSession session = sqlSessionFactory.openSession();
		UserDao userDao = session.getMapper(UserDao.class);
		List<Map> users= userDao.searchUserById("1");
		for(Map user:users){
			String userName = (String) user.get("USERNAME");
			System.out.println(userName);
		}
		session.close();
	}

     加载配置文件,打开session,拿到接口,执行SQL语句。这个过程就不说了。

     最后想要验证的是,逐个插入和批量插入的优缺点。本来是没有什么想验证的。批量插入怎么都要比逐个入的速度快吧?我也是这样想的。但是在做单元测试的时候发现了,批量插入比逐个插入慢的情况。而且在几台机子上都存在这样的情况。

     测试方法如下:
        @Test
	public void TestUserSaveBatchWithOneByOne()throws IOException{
		String resource = "META-INF/conf/mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		SqlSession session = sqlSessionFactory.openSession();
		UserDao userDao = session.getMapper(UserDao.class);
		ArrayList<HashMap<String, String>> users = new ArrayList<HashMap<String,String>>();
		int i=0;
		
		for(i=0;i<testTime;i++){
			HashMap<String, String> userMap = new HashMap<String, String>();
			userMap.put("id", CommonUtils.generateUUID());
			userMap.put("userName", "User"+i);
			users.add(userMap);
		}
		long beginTime=0,endTime=0;
		beginTime= System.currentTimeMillis();
		System.out.println(beginTime+":OneByOne begin!");
		for(HashMap<String, String> userMap : users){
			userDao.saveUserOneByOne(userMap);
		}
		endTime = System.currentTimeMillis();
		System.out.println(endTime+":OneByOne end!costs "+(endTime-beginTime)+"ms.");
		
		users.clear();
		
		for(i=0;i<testTime;i++){
			HashMap<String, String> userMap = new HashMap<String, String>();
			userMap.put("id", CommonUtils.generateUUID());
			userMap.put("userName", "User"+i);
			users.add(userMap);
		}
		beginTime = System.currentTimeMillis();
		System.out.println(beginTime+":Batch begin!");
		userDao.saveUsersBatch(users);
		endTime = System.currentTimeMillis();
		System.out.println(endTime+":Batch end!costs "+(endTime-beginTime)+"ms.");
		session.close();
	}


     testTime是测试次数,一个常量。先使用程序生成testTime个待测试对象,加入users队列中。执行逐个插入语句。打印消耗的时间。清空users,重新生成testTime个待测试对象,加入users,执行批量插入语句,打印消耗的时间。要提的一点是,我是使用程序生成的UUID而不是让程序的ID自增。这有可能成为批量插入在超过一定数量级后就慢于逐个插入的原因。

       testTime为500时,

      

       testTime为1000时,

       

         testTime为3000时,

        

         testTime为5000时,

        

     批量插入会随着插入个数的增加,插入速度急剧下降。这和机器的性能、内存有很大关系。因为批量插入在构建庞大的插入语句时,吃掉了大量的机器内存导致系统缓慢乃至宕机。所以在考虑使用批量插入的时候要考虑机器的性能。在批量插入的性能等于逐个插入时,切换回逐个插入。













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值