View Javadoc

1   /*
2    * Copyright 2006-2013 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.springframework.batch.item.database;
17  
18  import java.sql.SQLException;
19  import java.util.List;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.springframework.batch.item.ItemWriter;
24  import org.springframework.beans.factory.InitializingBean;
25  import org.springframework.dao.EmptyResultDataAccessException;
26  import org.springframework.dao.InvalidDataAccessResourceUsageException;
27  import org.springframework.orm.ibatis.SqlMapClientCallback;
28  import org.springframework.orm.ibatis.SqlMapClientTemplate;
29  import org.springframework.util.Assert;
30  
31  import com.ibatis.sqlmap.client.SqlMapClient;
32  import com.ibatis.sqlmap.client.SqlMapExecutor;
33  import com.ibatis.sqlmap.engine.execution.BatchException;
34  import com.ibatis.sqlmap.engine.execution.BatchResult;
35  
36  /**
37   * {@link ItemWriter} that uses the batching features from
38   * SqlMapClientTemplate to execute a batch of statements for all items
39   * provided.<br/>
40   *
41   * The user must provide an iBATIS statement id that points to the SQL statement defined
42   * in the iBATIS SqlMap configuration.<br/>
43   *
44   * It is expected that {@link #write(List)} is called inside a transaction.<br/>
45   *
46   * The writer is thread safe after its properties are set (normal singleton
47   * behavior), so it can be used to write in multiple concurrent transactions.
48   *
49   * @author Thomas Risberg
50   * @since 2.0
51   */
52  public class IbatisBatchItemWriter<T> implements ItemWriter<T>, InitializingBean {
53  
54  	protected static final Log logger = LogFactory.getLog(IbatisBatchItemWriter.class);
55  
56  	private SqlMapClientTemplate sqlMapClientTemplate;
57  
58  	private String statementId;
59  
60  	private boolean assertUpdates = true;
61  
62  	/**
63  	 * Public setter for the flag that determines whether an assertion is made
64  	 * that all items cause at least one row to be updated.
65  	 *
66  	 * @param assertUpdates the flag to set. Defaults to true;
67  	 */
68  	public void setAssertUpdates(boolean assertUpdates) {
69  		this.assertUpdates = assertUpdates;
70  	}
71  
72  	/**
73  	 * Public setter for {@link SqlMapClient} for injection purposes.
74  	 *
75  	 * @param sqlMapClient the SqlMapClient
76  	 */
77  	public void setSqlMapClient(SqlMapClient sqlMapClient) {
78  		if (sqlMapClientTemplate == null) {
79  			this.sqlMapClientTemplate = new SqlMapClientTemplate(sqlMapClient);
80  		}
81  	}
82  
83  	/**
84  	 * Public setter for the SqlMapClientTemplate.
85  	 *
86  	 * @param sqlMapClientTemplate the SqlMapClientTemplate
87  	 */
88  	public void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate) {
89  		this.sqlMapClientTemplate = sqlMapClientTemplate;
90  	}
91  
92  	/**
93  	 * Public setter for the statement id identifying the statement in the SqlMap
94  	 * configuration file.
95  	 *
96  	 * @param statementId the id for the statement
97  	 */
98  	public void setStatementId(String statementId) {
99  		this.statementId = statementId;
100 	}
101 
102 	/**
103 	 * Check mandatory properties - there must be an SqlMapClient and a statementId.
104 	 */
105 	@Override
106 	public void afterPropertiesSet() {
107 		Assert.notNull(sqlMapClientTemplate, "A SqlMapClient or a SqlMapClientTemplate is required.");
108 		Assert.notNull(statementId, "A statementId is required.");
109 	}
110 
111 	/* (non-Javadoc)
112 	 * @see org.springframework.batch.item.ItemWriter#write(java.util.List)
113 	 */
114 	@Override
115 	public void write(final List<? extends T> items) {
116 
117 		if (!items.isEmpty()) {
118 
119 			if (logger.isDebugEnabled()) {
120 				logger.debug("Executing batch with " + items.size() + " items.");
121 			}
122 
123 			@SuppressWarnings("unchecked")
124 			List<BatchResult> results = (List<BatchResult>) sqlMapClientTemplate.execute(
125 					new SqlMapClientCallback() {
126 						@Override
127 						public Object doInSqlMapClient(SqlMapExecutor executor)
128 								throws SQLException {
129 							executor.startBatch();
130 							for (T item : items) {
131 								executor.update(statementId, item);
132 							}
133 							try {
134 								return executor.executeBatchDetailed();
135 							} catch (BatchException e) {
136 								throw e.getBatchUpdateException();
137 							}
138 						}
139 					});
140 
141 			if (assertUpdates) {
142 				if (results.size() != 1) {
143 					throw new InvalidDataAccessResourceUsageException("Batch execution returned invalid results. " +
144 							"Expected 1 but number of BatchResult objects returned was " + results.size());
145 				}
146 
147 				int[] updateCounts = results.get(0).getUpdateCounts();
148 
149 				for (int i = 0; i < updateCounts.length; i++) {
150 					int value = updateCounts[i];
151 					if (value == 0) {
152 						throw new EmptyResultDataAccessException("Item " + i + " of " + updateCounts.length
153 								+ " did not update any rows: [" + items.get(i) + "]", 1);
154 					}
155 				}
156 			}
157 
158 		}
159 
160 	}
161 
162 }