Как реализовать пакетные операции с MyBatis/Spring?

Мне интересно, как реализовать пакетные операции с моими вставками с помощью 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);
    }
  }
}

Отказ от ответственности: это просто псевдокод для демонстрационных целей

Итак, что я могу сделать, чтобы превратить это в пакетный процесс?

В идеале я хочу иметь возможность сделать это с наименьшим "вторжением" в код, т.е. использовать аннотации более предпочтительно, но если это невозможно, то что лучше?

Кроме того, это нужно настроить только для этой службы, а не для всего, что в проекте.

Ответы

Ответ 1

Это пример запуска и тестирования... Обновление нескольких строк с помощью пакетной (ibatis + java)

В этом примере. Я обновляю количество посещений из таблицы с учетом partyid.

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 code

Фактический запрос, когда пакетный запуск

<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();

Ответ 2

Принятый ответ выше не дает вам пакетного режима для MyBatis. Вы должны выбрать надлежащего Исполнителя через ExecutorType.BATCH. Это либо передается как параметр SqlSession.openSession в стандартный MyBatis API, либо, если используется MyBatis- Spring, в качестве опции для SqlSessionTemplate. Это делается с помощью:

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

Больше ничего не нужно делать.

Ответ 3

Я не уверен, что я полностью понимаю вопрос, но я постараюсь дать вам свои мысли.

Для создания единой службы я бы рекомендовал создать сервисный интерфейс:

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

Затем вы можете проверить тип объекта и вызвать репозиторий карт-посредников.

Затем вы можете создать его больше, создав общий интерфейс:

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

и расширьте его с помощью интерфейса mapper:

public interface MyService extends Creator<MyRecord>{}

Теперь самый сложный шаг: вы получаете объект определенного типа, посмотрите, какой точный mapper реализует интерфейс Creator для этого класса (с использованием API отображения java) и вызывать конкретный метод.

Теперь я даю вам код, который я использую в одном из моих проектов:

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 для массовой вставки, как описано здесь.

Если вы считаете, что не хотите писать sql для каждого типа объектов, вам лучше использовать Hibernate или любую другую расширенную ORM. MyBatis - это просто интерфейс отображения SQL.