Mybatis传入参数为List对象时(foreach的用法/批量插入)

本文详细介绍了在Mybatis中如何使用foreach进行批量插入,并比较了三种批量插入方式(普通for循环、BATCH模式和foreach)的效率。通过实际项目案例展示了foreach在动态SQL拼接中的应用,同时提醒注意当数据量过大时可能遇到的MySQL SQL大小限制问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

>Mybatis传入参数为List对象时(foreach的用法/批量插入)

  1. 场景复现
    首先有如下一张表:
MySQL [test]> select * from t_entry_resource;
+----+-------------+------+----------+--------+--------+---------------------+
| id | resource_id | type | title    | banner | icon  | add_date            |
+----+-------------+------+----------+--------+--------+---------------------+
| 11 |          6  | 14   | 分类     | 1.jpg  | 2.jpg  | 2017-11-17 11:22:30 |
| 12 |          3  | 1    | 测试12   | 3.jpg  | 4.jpg  | 2017-11-17 11:22:30 |
| 13 |        653  | 1    | 测试34   | 5.jpg  | 6.jpg  | 2017-11-20 02:32:26 |
| 14 |          1  | 1    | 测试5    | 7.jpg  | 8.jpg  | 2017-11-20 02:32:51 |
| 15 |        3942 | 3    | 测试6    | 9.jpg  | 10.jpg | 2017-11-20 02:34:27 |
+----+-------------+------+----------+--------+--------+---------------------+
5 rows in set (0.01 sec)

如果要根据resource_id和type来批量查询记录,该如何编写Mybatis语句?
2. 解决方案
直接贴出来解决方案如下所示:

Dao层接口:

List<EntryResource> findByRidAndType(List<EntryResource> entryResources);

XML语句:

<select id="findByRidAndType" resultMap="entryResource" parameterType="list">
        SELECT
        *
        FROM
        t_entry_resource a
        WHERE
<foreach collection="list" index="index" item="entryResources" open="(" close=")" separator="or">
            ( `type`=#{entryResources.type} and resource_id=#{entryResources.resourceId} )
</foreach>

</select>

该语句利用了mybatis的foreach动态拼接SQL。

还有批量插入:

int insertBatch(@Param("list") List<AlarmDispose> disposeList);

<?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.boot.reservation.mapper.AlarmDisposeMapper">
    <sql id="tableName">
        alarm_dispose
    </sql>

    <sql id="baseColumn">
        id,alarm_inform_id,dispose_opinion,dispose_explain,dispose_person,accessory,picture,dispose_time,valid
    </sql>

    <insert id="insertBatch">
        INSERT INTO alarm_dispose
        (alarm_inform_id,dispose_opinion,dispose_explain,dispose_person,accessory,picture,dispose_time,valid)
        VALUES
        <foreach collection ="list" item="alarmDispose" separator =",">
            (#{alarmDispose.alarmInformId},
            #{alarmDispose.disposeOpinion},
            #{alarmDispose.disposeExplain},
            #{alarmDispose.disposePerson},
            #{alarmDispose.accessory},
            #{alarmDispose.picture},
            #{alarmDispose.disposeTime},
            #{alarmDispose.valid})
        </foreach >
    </insert>

</mapper>

结果:
在这里插入图片描述

  1. foreach属性
属性描述
item循环体中的具体对象。支持属性的点路径访问,如item.age,item.info.details。具体说明:在list和数组中是其中的对象,在map中是value。该参数为必选。
collection要做foreach的对象,作为入参时,List<?>对象默认用list代替作为键,数组对象有array代替作为键,Map对象用map代替作为键。当然在作为入参时可以使用@Param(“keyName”)来设置键,设置keyName后,list,array,map将会失效。 除了入参这种情况外,还有一种作为参数对象的某个字段的时候。举个例子:如果User有属性List ids。入参是User对象,那么这个collection = "ids"如果User有属性Ids ids;其中Ids是个对象,Ids有个属性List id;入参是User对象,那么collection = "ids.id"上面只是举例,具体collection等于什么,就看你想对那个元素做循环。该参数为必选。
separator元素之间的分隔符,例如在in()的时候,separator=","会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样。该参数可选。
openforeach代码的开始符号,一般是(和close=")"合用。常用在in(),values()时。该参数可选。
closeforeach代码的关闭符号,一般是)和open="("合用。常用在in(),values()时。该参数可选。
index在list和数组中,index是元素的序号,在map中,index是元素的key,该参数可选。
  1. foreach的几种用法
    (1) select count(*) from users id in (x1,x2,x3,…)
<select id="countByUserList" resultType="int" parameterType="list">    
select count(*) from users    
  <where>    
    id in    
    <foreach item="item" collection="list" separator="," open="(" close=")" index="">    
      #{item.id, jdbcType=NUMERIC}    
    </foreach>    
  </where>    
</select> 

(2) select count(*) from key_cols where col_a = ? AND col_b = ?

<select id="sel_key_cols" resultType="int">    
        select count(*) from key_cols where    
<foreach item="item" index="key" collection="map"  open="" separator="AND" close="">
        ${key} = #{item}
</foreach>    
</select>  

(3) select * from t_news n where n.tags like ? or n.tags like ?

<select id="selectTestForEach" parameterType="News" resultMap="NewsResultMapper">
  select * from t_news n where 
  <foreach collection="listTag" index="index" item="tag" open="" separator="or" close="">
            n.tags like  '%'||#{tag}||'%'
  </foreach>
<select>

转载地址:https://www.cnblogs.com/coderzhw/p/11094300.html

>关于mybatis的三种批量插入以及效率比较

1.表结构

CREATE TABLE `t_user` (
  `id` varchar(32) CHARACTER SET utf8 NOT NULL COMMENT '主键',
  `name` varchar(50) CHARACTER SET utf8 DEFAULT NULL COMMENT '用户名',
  `del_flag` char(1) CHARACTER SET utf8 DEFAULT NULL COMMENT '删除标示',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

2.1 jdbc.properties配置

mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://127.0.0.1:3306/ssm
mysql.username=root
mysql.password=admin
#定义初始连接数
mysql.initialSize=1
#定义最大连接数
mysql.maxActive=20
#定义最大空闲
mysql.maxIdle=20
#定义最小空闲
mysql.minIdle=1
#定义最长等待时间
mysql.maxWait=60000

2.2 spring-mybatis.xml配置

<context:component-scan base-package="com.win.ssm"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${mysql.driver}"/>
    <property name="url" value="${mysql.url}"/>
    <property name="username" value="${mysql.username}"/>
    <property name="password" value="${mysql.password}"/>
    <!-- 初始化链接大小-->
<property name="initialSize" value="${mysql.initialSize}"/>
    <!-- 连接池最大数量-->
<property name="maxActive" value="${mysql.maxActive}"/>
    <!-- 连接池最大空闲-->
<property name="maxIdle" value="${mysql.maxIdle}"/>
    <!-- 连接池最小空闲 -->
<property name="minIdle" value="${mysql.minIdle}"></property>
    <!-- 获取连接最大等待时间-->
<property name="maxWait" value="${mysql.maxWait}"/>
</bean>
<!-- spring与mybatis整合类 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!-- 查找接口的别名 -->
<property name="typeAliasesPackage" value="com.win"/>
    <!-- 自动扫描mapping.xml文件-->
<property name="mapperLocations" value="classpath:/mapping/*.xml"/>
</bean>

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory" />
    <!--<constructor-arg index="1" value="BATCH" />-->
</bean>

<!-- 扫描DAO接口 -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.win.ssm.dao"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- 事务管理 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

第一种:普通for循环插入

①junit类

@Test
public void testInsertBatch2() throws Exception {
    long start = System.currentTimeMillis();
    User user;
    SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(false);
    UserDao mapper = sqlSession.getMapper(UserDao.class);
    for (int i = 0; i < 500; i++) {
        user = new User();
        user.setId("test" + i);
        user.setName("name" + i);
        user.setDelFlag("0");
        mapper.insert(user);
    }
    sqlSession.commit();
    long end = System.currentTimeMillis();
    System.out.println("---------------" + (start - end) + "---------------");
}

②xml配置

<insert id="insert">
    INSERT INTO t_user (id, name, del_flag)
          VALUES(#{id}, #{name}, #{delFlag})
</insert>

第二种:mybatis BATCH模式插入

①junit类

@Test
public void testInsertBatch2() throws Exception {
    long start = System.currentTimeMillis();
    User user;
    SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);//跟上述sql区别
    UserDao mapper = sqlSession.getMapper(UserDao.class);
    for (int i = 0; i < 500; i++) {
        user = new User();
        user.setId("test" + i);
        user.setName("name" + i);
        user.setDelFlag("0");
        mapper.insert(user);
    }
    sqlSession.commit();
    long end = System.currentTimeMillis();
    System.out.println("---------------" + (start - end) + "---------------");
}

②xml配置与第一种②中使用相同

第三种:foreach方式插入

①junit类

@Test
public void testInsertBatch() throws Exception {
    long start = System.currentTimeMillis();
    List<User> list = new ArrayList<>();
    User user;
    for (int i = 0; i < 10000; i++) {
        user = new User();
        user.setId("test" + i);
        user.setName("name" + i);
        user.setDelFlag("0");
        list.add(user);
    }
    userService.insertBatch(list);
    long end = System.currentTimeMillis();
    System.out.println("---------------" + (start - end) + "---------------");
}

②xml配置

 <insert id="insertBatch">
    INSERT INTO t_user
            (id, name, del_flag)
    VALUES
    <foreach collection ="list" item="user" separator =",">
         (#{user.id}, #{user.name}, #{user.delFlag})
    </foreach >
</insert>

特别注意:mysql默认接受sql的大小是1048576(1M),即第三种方式若数据量超过1M会报如下异常:(可通过调整MySQL安装目录下的my.ini文件中[mysqld]段的"max_allowed_packet = 1M")

nested exception is com.mysql.jdbc.PacketTooBigException: Packet for query is too large (5677854 > 1048576).

You can change this value on the server by setting the max_allowed_packet' variable.

结果对比:

第一种第二种第二种
500条77427388622
1000条1529015078746
5000条780111773501172
10000条3974722011801205

项目实例:

service:

/**
 * DatePermission 表数据服务层接口
 */
public interface DatePermissionService {

    /**
     * 新增用户所具有的仓库
     */
    void save(Long userId,List<Long> list);

}

serviceimpl:

@Slf4j
@Service
public class DatePermissionServiceImpl implements DatePermissionService {

    @Autowired
    private DatePermissionMapper datePermissionMapper;

    @Override
    public void save(Long userId, List<Long> list) {

        List<DatePermissionEntity> entityList = new ArrayList<>();
        for (Long aLong : list) {
            DatePermissionEntity entity = new DatePermissionEntity();
            entity.setUserId(userId);
            entity.setWarehousesId(aLong);
            entityList.add(entity);
        }
        datePermissionMapper.insertBatch(entityList);
    }
}

mapper:

public interface DatePermissionMapper extends BaseMapper<DatePermissionEntity> {

    void insertBatch(List<DatePermissionEntity> list);
}

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="xxx.DatePermissionMapper">

    <insert id="insertBatch">
        INSERT INTO user_warehouses
        (user_id, warehouses_id)
        VALUES
        <foreach collection ="list" item="datePermissionEntity" separator =",">
            (#{datePermissionEntity.userId}, #{datePermissionEntity.warehousesId})
        </foreach >
    </insert>

</mapper>

实体类:

@TableName(value = "user_warehouses")
public class DatePermissionEntity extends BaseEntity {

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户Id
     */
    private Long userId;

    /**
     * 仓库Id
     */
    private Long warehousesId;

	get/set()

表字段:
在这里插入图片描述
测试结果:
在这里插入图片描述

### MyBatis 中使用 `foreach` 实现批量插入的写法 在 MyBatis 中,`foreach` 是一个非常强大的标签,可以用于循环处理集合数据。以下是一个完整的示例,展示如何使用 `foreach` 标签实现批量插入操作。 #### SQL Mapper 配置 在 MyBatis 的 XML 映射文件中,可以通过 `<insert>` 标签结合 `foreach` 来实现批量插入。以下是具体代码示例: ```xml <insert id="batchInsert" parameterType="java.util.List"> INSERT INTO info_notice (id, title, content, nick_name) VALUES <foreach collection="list" item="item" separator=","> (#{item.id,jdbcType=VARCHAR}, #{item.title,jdbcType=VARCHAR}, #{item.content,jdbcType=VARCHAR}, (SELECT nick_name FROM user_info WHERE user_id = #{item.userId,jdbcType=VARCHAR})) </foreach> </insert> ``` #### 说明 1. 上述代码中,`collection="list"` 表示传入参数是一个列表,`item="item"` 表示每次循环的单个元素[^1]。 2. `separator=","` 指定了每个插入语句之间的分隔符为逗号,确保生成的 SQL 语法正确[^2]。 3. 在插入的字段中,`nick_name` 是通过子查询从 `user_info` 表中动态获取的,避免了在服务层进行多次查询的操作[^1]。 #### Java 代码调用 在服务层或 DAO 层中,可以通过传递一个包含多个对象的列表来调用上述方法。例如: ```java List<Notice> noticeList = new ArrayList<>(); // 填充 noticeList 数据... noticeMapper.batchInsert(noticeList); ``` #### 注意事项 1. 如果数据库对单条 SQL 的长度有限制,则需要预估 `list` 的大小,并根据实际情况调整批量插入的策略[^3]。 2. 子查询 `(SELECT nick_name FROM user_info WHERE user_id = #{item.userId})` 可能会影响性能,建议在数据量较大优化查询逻辑或使用缓存机制[^1]。 #### 示例实体类 假设 `Notice` 类定义如下: ```java public class Notice { private String id; private String title; private String content; private String userId; // 对应 user_info 表中的 user_id // Getters and Setters } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值