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

来源:互联网 发布:查士丁尼瘟疫 知乎 编辑:程序博客网 时间:2024/05/01 19:53

     最近刚开发完一个项目,用了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语句。这个过程就不说了。

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

     测试方法如下:
        @Testpublic 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时,

        

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













原创粉丝点击