MyBatis ...在循环中获取最后插入的 ID "foreach"

MyBatis ... Get Last insert ID in loop "foreach"

谢谢你的帮助:)

我试图获取最后一个 ID,并阅读了很多关于它的文章 post,但我还没来得及在我的案例中应用它。

第一个Class

private Date date;
private List<AdsEntity> adsDetails;

    ... getters and setters

第二个Class(AdsEntity)

private int id;
private String description;

有我尝试获取最后一个 ID 的代码:

映射器

@Insert({
    "<script>",
    "INSERT INTO tb_ads_details  (idMyInfo, adDate)"
    + " VALUES"
        + " <foreach item='adsDetails' index='index' collection='adsDetails' separator=',' statement='SELECT LAST_INSERT_ID()' keyProperty='id' order='AFTER' resultType='java.lang.Integer'>"
            + " (#{adsDetails.description, jdbcType=INTEGER}) "
        + " </foreach>  ",  
    "</script>"})
void saveAdsDetails(@Param("adsDetails") List<AdsDetailsEntity> adsDetails);

在调试模式下,当我观看 List 时,我看到 id 仍然为 0 并且没有获得任何 id。

所以我写的没有锻炼:(


解决方案尝试了@Roman Konoval 的回答:

@Roman Konoval

我按照你说的做了, table 设置得很好 :) 还有一个问题,ID不符合

    @Insert("INSERT INTO tb_ads_details SET `idMyInfo` = #{adsDetail.idMyInfo, jdbcType=INTEGER}, `adDate` = #{adsDetail.adDate, jdbcType=DATE}")
    @SelectKey(statement = "SELECT LAST_INSERT_ID()", before = false, keyColumn = "id", keyProperty = "id", resultType = Integer.class )
    void saveAdsDetails(@Param("adsDetail") AdsDetailsEntity adsDetail);


    default void saveManyAdsDetails(@Param("adsDetails") List<AdsDetailsEntity> adsDetails)
    {
        for(AdsDetailsEntity adsDetail:adsDetails) {
            saveAdsDetails(adsDetail);
        }
    }

感谢您的帮助:)


解决方案添加到来自@Chris 建议的@Roman Konoval 提议

@Chris 和@Roman Konoval

    @Insert("INSERT INTO tb_ads_details SET `idMyInfo` = #{adsDetail.idMyInfo, jdbcType=INTEGER}, `adDate` = #{adsDetail.adDate, jdbcType=DATE}")
    @SelectKey(statement = "SELECT LAST_INSERT_ID()", before = false, keyColumn = "id", keyProperty = "adsDetail.id", resultType = int.class )
    void saveAdsDetails(@Param("adsDetail") AdsDetailsEntity adsDetail);




    default void saveManyAdsDetails(@Param("adsDetails") List<AdsDetailsEntity> adsDetails)
    {
        for(AdsDetailsEntity adsDetail:adsDetails) {
            saveAdsDetails(adsDetail); 
        }
    }

感谢大家的 3 条建议!!!

是的。没用。

请看mapper.dtd

foreach-tag 没有 support/provide 以下属性 statementkeyProperty orderresultType

如果您需要每个插入项目的 ID,请让您的 DataAccessObject 处理迭代并在您的 MapperInterface

中使用类似的东西
@Insert("INSERT INTO tb_ads_details (idMyInfo, adDate) (#{adsDetail.idMyInfo, jdbcType=INTEGER}, #{adsDetail.adDate, jdbcType=DATE})")
@SelectKey(before = false, keyColumn = "ID", keyProperty = "id", resultType = Integer.class, statement = { "SELECT LAST_INSERT_ID()" } )
void saveAdsDetails(@Param("adsDetail") AdsDetailsEntity adsDetail);

请确保 AdsDetailsEntity-Class 提供属性 idMyInfoadDate

编辑 2019-08-21 07:25

一些解释

提到提到的 dtd,<selectKey>-tag 只允许作为 <insert><update> 的直接子代。它指的是传递给映射器方法并声明为 parameterType.

的单个 Object

它只执行一次,它的 order 属性 告诉 myBatis 在 insert/update 语句之前或之后执行它。

在您的情况下,<script> 创建了一个语句,该语句被发送到数据库并由其处理。

允许@Insert<script>组合,<foreach>内部和@SelectKey组合。但是 myBatis 没有 intercept/observe/watch 数据库处理给定的语句。如前所述,@SelectKey@Insert 执行之前或之后仅执行一次。所以在你的特定情况下 @SelectKey returns 最后插入的元素的 id。如果您的脚本插入十个元素,则只会返回第十个元素的新生成的 id。但是 @SelectKey 需要 class-属性 以及 getter 和 setter 来将选定的 ID 放入 - List<?> 没有提供。

例子

假设您想保存一个 Advertisement 及其 AdvertisementDetails

Advertisement 有 ID、日期和详细信息

public class Advertisement {
    private List<AdvertisementDetail> adDetails;
    private Date date;
    private int id;

    public Advertisement() {
        super();
    }

    // getters and setters
}

AdvertisementDetail 有自己的 ID、描述和 Advertisement它所属的 ID

public class AdvertisementDetail {
    private String description;
    private int id;
    private int idAdvertisement;

    public AdvertisementDetail() {
        super();
    }

    // getters and setters
}

MyBatis-mapper 可能看起来像这样。 @Param没有用到,所以直接访问属性。

@Mapper
public interface AdvertisementMapper {
    @Insert("INSERT INTO tb_ads (date) (#{date, jdbcType=DATE})")
    @SelectKey(
            before = false,
            keyColumn = "ID",
            keyProperty = "id",
            resultType = Integer.class,
            statement = { "SELECT LAST_INSERT_ID()" })
    void insertAdvertisement(
            Advertisement ad);

    @Insert("INSERT INTO tb_ads_details (idAdvertisement, description) (#{idAdvertisement, jdbcType=INTEGER}, #{description, jdbcType=VARCHAR})")
    @SelectKey(
            before = false,
            keyColumn = "ID",
            keyProperty = "id",
            resultType = Integer.class,
            statement = { "SELECT LAST_INSERT_ID()" })
    void insertAdvertisementDetail(
            AdvertisementDetail adDetail);
}

DataAccessObject (DAO) 可能看起来像这样

@Component
public class DAOAdvertisement {
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    public DAOAdvertisement() {
        super();
    }

    public void save(
            final Advertisement advertisement) {
        try (SqlSession session = this.sqlSessionFactory.openSession(false)) {
            final AdvertisementMapper mapper = session.getMapper(AdvertisementMapper.class);
            // insert the advertisement (if you have to)
            // its new generated id is received via @SelectKey
            mapper.insertAdvertisement(advertisement);
            for (final AdvertisementDetail adDetail : advertisement.getAdDetails()) {
                // set new generated advertisement-id
                adDetail.setIdAdvertisement(advertisement.getId());
                // insert adDetail
                // its new generated id is received via @SelectKey
                mapper.insertAdvertisementDetail(adDetail);
            }
            session.commit();
        } catch (final PersistenceException e) {
            e.printStackTrace();
        }
    }
}

Chris 在 foreach 中写的关于无法获取 ID 的内容是正确的。然而,有一种方法可以在映射器中实现 id 获取,而无需在外部进行。如果您使用 say spring 并且没有单独的 DAO 层并且您的 mybatis 映射器是存储库,这可能会有所帮助。

您可以使用 default interface method (see another tutorial 关于它们)通过为单个项目插入调用映射器方法来插入项目列表,并且单个项目插入方法本身会选择 id:

interface ItemMapper {
  @Insert({"insert into myitem (item_column1, item_column2, ...)"})
  @SelectKey(before = false, keyColumn = "ID", 
     keyProperty = "id", resultType = Integer.class, 
     statement = { "SELECT LAST_INSERT_ID()" } )
  void saveItem(@Param("item") Item item);

  default void saveItems(@Param("items") List<Item> items) {
    for(Item item:items) {
      saveItem(item);
  }
}

MyBatis 可以将生成的键分配给列表参数 if 你的 DB/driver 通过 java.sql.Statement#getGeneratedKeys() 支持多个生成的键(MS SQL 服务器,例如,does not support it,ATM)。

以下示例使用 MySQL 5.7.27 + Connector/J 8.0.17 进行测试(您应该在问题中包含版本信息)。
请务必使用最新版本的 MyBatis (=3.5.2),因为最近有一些规范更改和错误修复。

Table定义:

CREATE TABLE le tb_ads_details (
  id INT PRIMARY KEY AUTO_INCREMENT,
  description VARCHAR(32)
)

POJO:

private class AdsDetailsEntity {
  private int id;
  private String description;
  // getters/setters
}

映射器方法:

@Insert({
  "<script>",
  "INSERT INTO tb_ads_details (description) VALUES",
  "<foreach item='detail' collection='adsDetails' separator=','>",
  "  (#{detail.description})",
  "</foreach>",
  "</script>"
})
@Options(useGeneratedKeys = true, keyProperty="adsDetails.id", keyColumn="id")
void saveAdsDetails(@Param("adsDetails") List<AdsDetailsEntity> adsDetails);

注意:当插入大量行时,您应该使用批量插入(使用ExecutorType.BATCH)而不是多行插入(=<foreach/>)。