1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 org.apache.ibatis.session.ExecutorType;
22 import org.apache.ibatis.session.SqlSession;
23 import org.apache.ibatis.session.SqlSessionFactory;
24 import org.mybatis.logging.Logger;
25 import org.mybatis.logging.LoggerFactory;
26 import org.mybatis.spring.SqlSessionTemplate;
27 import org.springframework.batch.item.Chunk;
28 import org.springframework.batch.item.ItemWriter;
29 import org.springframework.beans.factory.InitializingBean;
30 import org.springframework.core.convert.converter.Converter;
31 import org.springframework.dao.EmptyResultDataAccessException;
32 import org.springframework.dao.InvalidDataAccessResourceUsageException;
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 public class MyBatisBatchItemWriter<T> implements ItemWriter<T>, InitializingBean {
53
54 private static final Logger LOGGER = LoggerFactory.getLogger(MyBatisBatchItemWriter.class);
55
56 private SqlSessionTemplate sqlSessionTemplate;
57
58 private String statementId;
59
60 private boolean assertUpdates = true;
61
62 private Converter<T, ?> itemToParameterConverter = new PassThroughConverter<>();
63
64
65
66
67
68
69
70
71 public void setAssertUpdates(boolean assertUpdates) {
72 this.assertUpdates = assertUpdates;
73 }
74
75
76
77
78
79
80
81 public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
82 if (sqlSessionTemplate == null) {
83 this.sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
84 }
85 }
86
87
88
89
90
91
92
93 public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
94 this.sqlSessionTemplate = sqlSessionTemplate;
95 }
96
97
98
99
100
101
102
103 public void setStatementId(String statementId) {
104 this.statementId = statementId;
105 }
106
107
108
109
110
111
112
113
114
115
116
117 public void setItemToParameterConverter(Converter<T, ?> itemToParameterConverter) {
118 this.itemToParameterConverter = itemToParameterConverter;
119 }
120
121
122
123
124 @Override
125 public void afterPropertiesSet() {
126 notNull(sqlSessionTemplate, "A SqlSessionFactory or a SqlSessionTemplate is required.");
127 isTrue(ExecutorType.BATCH == sqlSessionTemplate.getExecutorType(),
128 "SqlSessionTemplate's executor type must be BATCH");
129 notNull(statementId, "A statementId is required.");
130 notNull(itemToParameterConverter, "A itemToParameterConverter is required.");
131 }
132
133 @Override
134 public void write(final Chunk<? extends T> items) {
135
136 if (!items.isEmpty()) {
137 LOGGER.debug(() -> "Executing batch with " + items.size() + " items.");
138
139 for (T item : items) {
140 sqlSessionTemplate.update(statementId, itemToParameterConverter.convert(item));
141 }
142
143 var results = sqlSessionTemplate.flushStatements();
144
145 if (assertUpdates) {
146 if (results.size() != 1) {
147 throw new InvalidDataAccessResourceUsageException("Batch execution returned invalid results. "
148 + "Expected 1 but number of BatchResult objects returned was " + results.size());
149 }
150
151 var updateCounts = results.get(0).getUpdateCounts();
152
153 for (var i = 0; i < updateCounts.length; i++) {
154 var value = updateCounts[i];
155 if (value == 0) {
156 throw new EmptyResultDataAccessException("Item " + i + " of " + updateCounts.length
157 + " did not update any rows: [" + items.getItems().get(i) + "]", 1);
158 }
159 }
160 }
161 }
162 }
163
164 private static class PassThroughConverter<T> implements Converter<T, T> {
165
166 @Override
167 public T convert(T source) {
168 return source;
169 }
170
171 }
172
173 }