web-dev-qa-db-fra.com

Comment implémenter des opérations batch avec MyBatis / Spring?

Je me demande comment implémenter des opérations par lots avec mes instructions d'insertion à l'aide de MyBatis 3 et Spring 3?

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

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

Avis de non-responsabilité: ce n'est qu'un pseudo-code à des fins de démonstration

Alors, que puis-je faire pour transformer cela en un processus par lots?

Idéalement, je veux pouvoir le faire avec le moins "d'intrusion" dans le code, c'est-à-dire utiliser des annotations plus préférées, mais si ce n'est pas possible, quelle est la prochaine meilleure chose?

En outre, cela doit être configuré uniquement pour ce service, pas pour tout dans le projet.

12
Trant

Ceci est un exemple en cours d'exécution et testé ... Mettre à jour plusieurs lignes à l'aide de batch (ibatis + Java)

Dans cet ex. Je suis en train de mettre à jour le nombre de participants de la table avec le respectif à 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();
    }
}

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 batch s'exécute

<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 mon code de session batch

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

SqlSession session = ConnectionBuilderAction.getSqlSession();
10
Sameer Kazi

La réponse acceptée ci-dessus n'obtient pas réellement le mode batch pour MyBatis. Vous devez choisir l'exécuteur approprié via ExecutorType.BATCH. Cela est transmis en tant que paramètre à SqlSession.openSession dans l'API MyBatis standard ou, si vous utilisez MyBatis-Spring, en tant qu'option pour le 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.

9
jkratz

Je ne suis pas sûr d'avoir bien compris la question, mais je vais essayer de vous donner mon avis.

Pour faire le service unique, je recommanderais de générer l'interface de service:

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

Ensuite, vous pouvez vérifier le type de l'objet et appeler le référentiel du mappeur propper.

Ensuite, vous pouvez le générer davantage en créant une interface commune:

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

et étendez-le par votre interface de mappage:

public interface MyService extends Creator<MyRecord>{}

Maintenant, l'étape la plus compliquée: vous obtenez l'objet d'un type particulier, voyez quel mappeur exact implémente l'interface Creator pour cette classe (en utilisant Java API de réflexion) et appelez la méthode particulière.

Maintenant, je vous donne 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));
    }
}

Avertissement! Cette approche peut avoir un impact négatif sur les performances, utilisez-la donc dans des actions non critiques pour les performances

Si vous voulez un insert en vrac, je recommanderais d'utiliser mybatis foreach pour l'insertion en vrac comme décrit ici .

Si vous pensez que vous ne voulez pas écrire SQL pour tous les types d'objets, il vaut mieux utiliser Hibernate ou tout autre ORM avancé. MyBatis n'est qu'une interface de mappage SQL.

2
Nailgun