【问题标题】:How to implement batch operations with MyBatis/Spring?如何用 MyBatis/Spring 实现批量操作?
【发布时间】:2013-07-29 16:06:14
【问题描述】:

我想知道如何使用 MyBatis 3 和 Spring 3 使用我的插入语句实现批处理操作?

例如,这是当前正在做的事情:

spring.xml:

<bean id="jndiTemplateDatasource" class="org.springframework.jndi.JndiTemplate">
    <property name="environment">
      <props>
        <prop key="java.naming.factory.initial">${context.factory}</prop>
      </props>
    </property>
</bean>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiTemplate" ref="jndiTemplateDatasource"/>
  <property name="jndiName" value="${connectionpool.jndi}"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <property name="basePackage" value="com.test" />
</bean>

MyService.xml:

<insert id="insertMyRecord" parameterType="com.test.MyRecord"  >
   insert into ... // code removed
</insert> 

MyService.java:

public interface MyService {

  public void insertMyRecord (MyRecord);
}

MyController.java:

@Controller
public class MyController {

  @Autowired
  private MyService myService;

  @Transactional
  @RequestMapping( .... )
  public void bulkUpload (@RequestBody List<MyRecord> myRecords) {
    for (MyRecord record : myRecords) {
      myService.insertMyRecord(record);
    }
  }
}

免责声明:这只是用于演示目的的伪代码

那么我该怎么做才能把它变成一个批处理呢?

理想情况下,我希望能够以最少的“侵入”代码来做到这一点,即更喜欢使用注释,但如果不可能的话,下一个最好的事情是什么?

此外,这需要仅针对这一服务进行配置,而不是针对项目中的所有内容。

【问题讨论】:

  • 更明确地说,目标是在记录插入中获得 JDBC 批处理类型的处理以实现快速性能,而不是它没有的一对一方法

标签: java spring mybatis


【解决方案1】:

上面接受的答案实际上并没有为您提供 MyBatis 的批处理模式。您需要通过 ExecutorType.BATCH 选择合适的 Executor。这要么作为参数传递给标准 MyBatis API 中的 SqlSession.openSession,或者,如果使用 MyBatis-Spring,作为 SqlSessionTemplate 的选项。这是通过:

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

没有其他事情需要做。

【讨论】:

  • 不要,用这个XML配置,我的批量插入执行很慢,和一个一个插入没有区别。
【解决方案2】:

这是正在运行和测试的示例... 使用批处理更新多行(ibatis + java)

在这个例子中。我正在将表中的出席人数更新为对应的派对。

public static int updateBatch(List<MyModel> attendingUsrList) {
    SqlSession session = ConnectionBuilderAction.getSqlSession();
    PartyDao partyDao = session.getMapper(PartyDao.class);
    try {
        if (attendingUsrList.size() > 0) {
            partyDao.updateAttendingCountForParties(attendingUsrList);
        }
        session.commit();
    } catch (Throwable t) {
        session.rollback();
        logger.error("Exception occurred during updateBatch : ", t);
        throw new PersistenceException(t);
    } finally {
        session.close();
    }
}

定义变量的模型类:

public class MyModel  {

    private long attending_count;
    private String eid;

    public String getEid() {
        return eid;
    }

    public void setEid(String eid) {
        this.eid = eid;
    }

    public long getAttending_count() {
        return attending_count;
    }

    public void setAttending_count(long attending_count) {
        this.attending_count = attending_count;
    }


}

party.xml 代码

批量执行的实际查询

<foreach collection="attendingUsrList" item="model"  separator=";">
    UPDATE parties SET attending_user_count = #{model.attending_count}
    WHERE  fb_party_id = #{model.eid}  
</foreach>

接口代码在这里

public interface PartyDao {
    int updateAttendingCountForParties (@Param("attendingUsrList") List<FBEventModel>attendingUsrList);
}

这是我的批处理会话代码

public static synchronized SqlSession getSqlBatchSession() {
    ConnectionBuilderAction connection = new ConnectionBuilderAction();
    sf = connection.getConnection();
    SqlSession session = sf.openSession(ExecutorType.BATCH);
    return session;
}

SqlSession session = ConnectionBuilderAction.getSqlSession();

【讨论】:

  • 这里缺少的主要信息是您需要在打开 SqlSession 时使用 ExecutorType.BATCH。在标准的 MyBatis API 中,它是 SqlSession 类的 openSession 的一个参数。如果您使用的是 MyBatis-Spring,您可以将其作为参数传递给您的 SqlSessionTemplate。即:
  • 在底部定义getSqlBatchSession,但在代码中调用getSqlSession。这是笔误吗?
  • 不应使用 foreach。更有效的方法见这里:stackoverflow.com/a/29264696/922348
【解决方案3】:

我不确定我是否完全正确地理解了这个问题,但我会尽力告诉你我的想法。

为了制作单一服务,我建议生成服务接口:

public void bulkUpload (@RequestBody List<T> myRecords) 

然后您可以检查对象的类型并调用propper映射器存储库。

然后你可以通过创建一个通用接口来更加泛化它:

public interface Creator<T> {
    void create(T object);
}

并通过您的映射器接口对其进行扩展:

public interface MyService extends Creator<MyRecord>{}

现在是最复杂的一步:获取特定类型的对象,查看哪个映射器为该类实现了 Creator 接口(使用 java 反射 API)并调用特定方法。

现在我给你我在我的一个项目中使用的代码:

package com.mydomain.repository;

//imports ...
import org.reflections.Reflections;

@Repository(value = "dao")
public class MyBatisDao {

    private static final Reflections REFLECTIONS = new Reflections("com.mydomain");

    @Autowired
    public SqlSessionManager sqlSessionManager;

    public void create(Object o) {
        Creator creator = getSpecialMapper(Creator.class, o);
        creator.create(o);
    }

    // other CRUD methods

    @SuppressWarnings("unchecked")
    private <T> T getSpecialMapper(Class<T> specialClass, Object parameterObject) {
        Class parameterClass = parameterObject.getClass();
        Class<T> mapperClass = getSubInterfaceParametrizedWith(specialClass, parameterClass);
        return sqlSessionManager.getMapper(mapperClass);
    }

    private static <T, P> Class<? extends T> getSubInterfaceParametrizedWith(Class<T> superInterface, Class<P> parameterType) {
        Set<Class<? extends T>> subInterfaces = REFLECTIONS.getSubTypesOf(superInterface);
        for (Class<? extends T> subInterface: subInterfaces) {
            for (Type genericInterface : subInterface.getGenericInterfaces()) {
                if (!(genericInterface instanceof ParameterizedType)) continue;
                ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
                Type rawType = parameterizedType.getRawType();
                if (rawType instanceof Class<?> && ((Class<?>) rawType).isAssignableFrom(superInterface)) {
                    for (Type type: parameterizedType.getActualTypeArguments()) {
                        if (type instanceof Class<?> && ((Class<?>) type).isAssignableFrom(parameterType)) {
                            return subInterface;
                        }
                    }
                }

            }
        }
        throw new IllegalStateException(String.format("No extension of %s found for parametrized type %s ", superInterface, parameterType));
    }
}

警告!这种方法可能会对性能产生不良影响,因此请在非性能关键操作中使用它

如果您想要批量插入,我建议使用 mybatis foreach 进行批量插入,如 here 所述。

如果您认为您不想为每种类型的对象编写 sql,您最好使用 Hibernate 或任何其他高级 ORM。 MyBatis 只是一个 SQL 映射接口。

【讨论】:

  • 非常好的信息,我最终可能会使用它,但它并没有解决实现批处理的愿望。我想要的是,出于性能原因,我的插入循环处理类似于 JDBC 批处理,而不是现在处理每个插入都是单个事务
  • @Trant 那么这是否意味着您需要将批量插入与 mybatis foreach 一起使用(我在上面给出的链接)?
  • 但是 MyBatis 是把它变成一个优化的批处理操作还是仍然做一个一对一的插入?
  • @Trant JDBC 批处理一次发送多个语句并且不进行任何其他优化。如果您在映射器 xml 中的一个方法中包含多个 SQL 语句,MyBatis 也会这样做。如果您的应用使用一条语句插入多行(如链接的 StackOverflow 问题中所述),它的性能当然会更好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多