12 votes

Comment mettre en œuvre des opérations par lots avec MyBatis/Spring ?

Je me demande comment implémenter des opérations par lots avec mes instructions d'insertion en utilisant MyBatis 3 et Spring 3 ?

A titre d'exemple, voici ce qui se fait actuellement :

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

MonContrôleur.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);
    }
  }
}

Avertissement : il s'agit d'un pseudo-code à des fins de démonstration.

Que puis-je faire pour transformer cela en un processus par lots ?

Idéalement, je voudrais pouvoir le faire avec le moins d'"intrusion" possible dans le code, c'est-à-dire en utilisant de préférence des annotations, mais si ce n'est pas possible, quelle est la meilleure solution suivante ?

En outre, cette configuration ne doit être appliquée qu'à ce seul service, et non à l'ensemble du projet.

11voto

jkratz Points 373

La réponse acceptée ci-dessus ne permet pas d'obtenir le mode batch pour MyBatis. Vous devez choisir l'exécuteur approprié via ExecutorType.BATCH. Celui-ci est soit passé en paramètre de SqlSession.openSession dans l'API standard de MyBatis, soit, si l'on utilise MyBatis-Spring, en tant qu'option du SqlSessionTemplate. Cela se fait via :

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

Il n'y a rien d'autre à faire.

10voto

Sameer Kazi Points 835

Cet exemple est en cours d'exécution et testé ... Mise à jour de plusieurs lignes par batch (ibatis + java )

Dans cet ex. Je mets à jour le nombre de participants à partir du tableau correspondant à l'identifiant du parti.

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

Classe de modèle où la variable est définie :

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;
    }

}

code party.xml

Requête réelle où le lot est exécuté

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

Code d'interface ici

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

Voici le code de ma session de traitement par lots

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

SqlSession session = ConnectionBuilderAction.getSqlSession();

2voto

Nailgun Points 1118

Je ne suis pas sûr d'avoir bien compris la question, mais je vais essayer de vous faire part de mes réflexions.

Pour créer un service unique, je recommanderais de généraliser l'interface du service :

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

Vous pouvez alors vérifier le type de l'objet et appeler le référentiel propper mapper.

Il est ensuite possible de le généraliser en créant une interface commune :

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

et l'étendre avec votre interface de mappeur :

public interface MyService extends Creator<MyRecord>{}

Voici maintenant l'étape la plus compliquée : vous obtenez l'objet d'un type particulier, vous voyez quel mappeur exact met en œuvre l'interface Creator pour cette classe (à l'aide de l'API de réflexion Java) et vous invoquez la méthode en question.

Je vous donne maintenant le code que j'utilise dans l'un de mes projets :

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

Attention ! Cette approche peut avoir un impact négatif sur les performances ; il convient donc de l'utiliser pour les actions non critiques en termes de performances.

Si vous souhaitez effectuer des insertions en masse, je vous recommande d'utiliser mybatis foreach pour les insertions en masse, comme décrit ci-dessous. aquí .

Si vous pensez que vous ne voulez pas écrire du code SQL pour chaque type d'objet, vous feriez mieux d'utiliser Hibernate ou tout autre ORM avancé. MyBatis est juste une interface de mapping SQL.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X