View Javadoc
1   /**
2    *    Copyright 2010-2015 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.mybatis.spring.batch;
17  
18  import static org.springframework.util.Assert.isTrue;
19  import static org.springframework.util.Assert.notNull;
20  
21  import java.util.List;
22  
23  import org.apache.ibatis.executor.BatchResult;
24  import org.apache.ibatis.logging.Log;
25  import org.apache.ibatis.logging.LogFactory;
26  import org.apache.ibatis.session.ExecutorType;
27  import org.apache.ibatis.session.SqlSessionFactory;
28  import org.mybatis.spring.SqlSessionTemplate;
29  import org.springframework.batch.item.ItemWriter;
30  import org.springframework.beans.factory.InitializingBean;
31  import org.springframework.dao.EmptyResultDataAccessException;
32  import org.springframework.dao.InvalidDataAccessResourceUsageException;
33  
34  /**
35   * {@code ItemWriter} that uses the batching features from
36   * {@code SqlSessionTemplate} to execute a batch of statements for all items
37   * provided.<br/>
38   *
39   * Provided to facilitate the migration from Spring-Batch iBATIS 2 writers to MyBatis 3<br/>
40   *
41   * The user must provide a MyBatis statement id that points to the SQL statement defined
42   * in the MyBatis.<br/>
43   *
44   * It is expected that {@link #write(List)} is called inside a transaction. If it is not
45   * each statement call will be autocommitted and flushStatements will return no results.<br/>
46   *
47   * The writer is thread safe after its properties are set (normal singleton
48   * behavior), so it can be used to write in multiple concurrent transactions.
49   *
50   * @author Eduardo Macarron
51   * 
52   * @since 1.1.0
53   * @version $Id$
54   */
55  public class MyBatisBatchItemWriter<T> implements ItemWriter<T>, InitializingBean {
56  
57    private static final Log LOGGER = LogFactory.getLog(MyBatisBatchItemWriter.class);
58  
59    private SqlSessionTemplate sqlSessionTemplate;
60  
61    private String statementId;
62  
63    private boolean assertUpdates = true;
64  
65    /**
66     * Public setter for the flag that determines whether an assertion is made
67     * that all items cause at least one row to be updated.
68     *
69     * @param assertUpdates the flag to set. Defaults to true;
70     */
71    public void setAssertUpdates(boolean assertUpdates) {
72      this.assertUpdates = assertUpdates;
73    }
74  
75    /**
76     * Public setter for {@link SqlSessionFactory} for injection purposes.
77     *
78     * @param SqlSessionFactory sqlSessionFactory
79     */
80    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
81      if (sqlSessionTemplate == null) {
82        this.sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
83      }
84    }
85  
86    /**
87     * Public setter for the {@link SqlSessionTemplate}.
88     *
89     * @param SqlSessionTemplate the SqlSessionTemplate
90     */
91    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
92      this.sqlSessionTemplate = sqlSessionTemplate;
93    }
94  
95    /**
96     * Public setter for the statement id identifying the statement in the SqlMap
97     * configuration file.
98     *
99     * @param statementId the id for the statement
100    */
101   public void setStatementId(String statementId) {
102     this.statementId = statementId;
103   }
104 
105   /**
106    * Check mandatory properties - there must be an SqlSession and a statementId.
107    */
108   @Override
109   public void afterPropertiesSet() {
110     notNull(sqlSessionTemplate, "A SqlSessionFactory or a SqlSessionTemplate is required.");
111     isTrue(ExecutorType.BATCH == sqlSessionTemplate.getExecutorType(), "SqlSessionTemplate's executor type must be BATCH");
112     notNull(statementId, "A statementId is required.");
113   }
114 
115   /**
116    * {@inheritDoc}
117    */
118   @Override
119   public void write(final List<? extends T> items) {
120 
121     if (!items.isEmpty()) {
122 
123       if (LOGGER.isDebugEnabled()) {
124         LOGGER.debug("Executing batch with " + items.size() + " items.");
125       }
126 
127       for (T item : items) {
128         sqlSessionTemplate.update(statementId, item);
129       }
130 
131       List<BatchResult> results = sqlSessionTemplate.flushStatements();
132 
133       if (assertUpdates) {
134         if (results.size() != 1) {
135           throw new InvalidDataAccessResourceUsageException("Batch execution returned invalid results. " +
136               "Expected 1 but number of BatchResult objects returned was " + results.size());
137         }
138 
139         int[] updateCounts = results.get(0).getUpdateCounts();
140 
141         for (int i = 0; i < updateCounts.length; i++) {
142           int value = updateCounts[i];
143           if (value == 0) {
144             throw new EmptyResultDataAccessException("Item " + i + " of " + updateCounts.length
145                 + " did not update any rows: [" + items.get(i) + "]", 1);
146           }
147         }
148       }
149     }
150   }
151 
152 }