DbUnit测试数据XML格式常用的就两种,FlatXmlDataset和XmlDataSet:
<dataset> <USER USER_ID="1" USER_NAME="ZhuTou" PASSWORD="zt"/> </dataset>
<dataset> <table name="USER"> <column>USER_ID</column> <column>USER_NAME</column> <column>PASSWORD</column> <row> <value>1</value> <value>ZhuTou</value> <value>zt</value> </row> </table> </dataset>
第一种比较简洁,某些空的字段可以直接不写,但是不支持CDATA,如果有些数据较长或是二进制需要使用BASE64编码,就用不了了。
第二种就是支持CDATA,缺点是空的字段也要写一个<NULL/>或<NONE/>,不能少,顺序也要和column定义一致,不能乱。这样不但烦琐(特别是某些表很多字段可能为空的情况),还容易出错,一堆数据很难看出哪行是对应哪一个字段的。
我期望的格式是
<dataset> <table name="user"> <column>user_id</column> <column>user_name</column> <column>password</column> <column>address</column> <column>remark</column> <row> <value name="user_id">1</value> <value name="user_name">ZhuTou</value> <value name="password"><![CDATA[jfl3<0d/;]]></value> </row> ... </table> <dataset>
而且row里的的顺序可以乱排。
看了DBUNIT部分代码(主要是org.dbunit.dataset.xml.XmlProducer和org.dbunit.dataset.xml.XmlDataSet),只要自己实现一个XmlProducer就可以了,不过因为XmlDataSet里无法指定producer所以只要把XmlDataSet也重写一个。
CustomXmlProducer:
1 import java.io.IOException; 2 import java.io.InputStream; 3 import java.util.HashMap; 4 import java.util.LinkedList; 5 import java.util.List; 6 import java.util.Map; 7 8 import javax.xml.parsers.ParserConfigurationException; 9 import javax.xml.parsers.SAXParserFactory; 10 11 import org.dbunit.dataset.Column; 12 import org.dbunit.dataset.DataSetException; 13 import org.dbunit.dataset.DefaultTableMetaData; 14 import org.dbunit.dataset.ITableMetaData; 15 import org.dbunit.dataset.datatype.DataType; 16 import org.dbunit.dataset.stream.DefaultConsumer; 17 import org.dbunit.dataset.stream.IDataSetConsumer; 18 import org.dbunit.dataset.stream.IDataSetProducer; 19 import org.slf4j.Logger; 20 import org.slf4j.LoggerFactory; 21 import org.xml.sax.Attributes; 22 import org.xml.sax.ContentHandler; 23 import org.xml.sax.ErrorHandler; 24 import org.xml.sax.InputSource; 25 import org.xml.sax.SAXException; 26 import org.xml.sax.SAXParseException; 27 import org.xml.sax.XMLReader; 28 import org.xml.sax.helpers.DefaultHandler; 29 30 public class CustomXmlProducer extends DefaultHandler implements 31 IDataSetProducer, ContentHandler, ErrorHandler { 32 33 /** 34 * Logger for this class 35 */ 36 private static final Logger logger = LoggerFactory 37 .getLogger(CustomXmlProducer.class); 38 39 private static final IDataSetConsumer EMPTY_CONSUMER = new DefaultConsumer(); 40 41 private static final String DATASET = "dataset"; 42 private static final String TABLE = "table"; 43 private static final String NAME = "name"; 44 private static final String COLUMN = "column"; 45 private static final String ROW = "row"; 46 private static final String VALUE = "value"; 47 // private static final String NULL = "null"; 48 // private static final String NONE = "none"; 49 50 private static final String COLUMN_ATTR_NAME = "name"; 51 52 private final InputSource _inputSource; 53 private boolean _validating = false; 54 55 private IDataSetConsumer _consumer = EMPTY_CONSUMER; 56 57 private String _activeTableName; 58 private ITableMetaData _activeMetaData; 59 60 private List<String> _activeColumnNames; 61 private StringBuffer _activeCharacters; 62 // private List _activeRowValues; 63 64 private Map<String, String> _activeRowValues; 65 private String _activeColumnName; 66 private List<String> __activeColumnNames; 67 68 public CustomXmlProducer(InputSource inputSource) { 69 _inputSource = inputSource; 70 } 71 72 private ITableMetaData createMetaData(String tableName, List<String> columnNames) { 73 logger.debug("createMetaData(tableName={}, _columnNames={}) - start", 74 tableName, columnNames); 75 76 Column[] columns = new Column[columnNames.size()]; 77 for (int i = 0; i < columns.length; i++) { 78 String columnName = (String) columnNames.get(i); 79 columns[i] = new Column(columnName, DataType.UNKNOWN); 80 } 81 DefaultTableMetaData metaData = new DefaultTableMetaData(tableName, 82 columns); 83 return metaData; 84 } 85 86 public void setValidating(boolean validating) { 87 _validating = validating; 88 } 89 90 // ////////////////////////////////////////////////////////////////////////// 91 // IDataSetProducer interface 92 93 public void setConsumer(IDataSetConsumer consumer) throws DataSetException { 94 logger.debug("setConsumer(consumer={}) - start", consumer); 95 _consumer = consumer; 96 } 97 98 public void produce() throws DataSetException { 99 logger.debug("produce() - start"); 100 101 try { 102 SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); 103 saxParserFactory.setValidating(_validating); 104 XMLReader xmlReader = saxParserFactory.newSAXParser() 105 .getXMLReader(); 106 107 xmlReader.setContentHandler(this); 108 xmlReader.setEntityResolver(this); 109 xmlReader.setErrorHandler(this); 110 xmlReader.parse(_inputSource); 111 } catch (ParserConfigurationException e) { 112 throw new DataSetException(e); 113 } catch (SAXException e) { 114 DataSetException exceptionToRethrow = CustomXmlProducer 115 .buildException(e); 116 throw exceptionToRethrow; 117 } catch (IOException e) { 118 throw new DataSetException(e); 119 } 120 } 121 122 /** 123 * Wraps a {@link SAXException} into a {@link DataSetException} 124 * 125 * @param cause 126 * The cause to be wrapped into a {@link DataSetException} 127 * @return A {@link DataSetException} that wraps the given 128 * {@link SAXException} 129 */ 130 protected final static DataSetException buildException(SAXException cause) { 131 int lineNumber = -1; 132 if (cause instanceof SAXParseException) { 133 lineNumber = ((SAXParseException) cause).getLineNumber(); 134 } 135 Exception exception = cause.getException() == null ? cause : cause 136 .getException(); 137 String message; 138 139 if (lineNumber >= 0) { 140 message = "Line " + lineNumber + ": " + exception.getMessage(); 141 } else { 142 message = exception.getMessage(); 143 } 144 145 if (exception instanceof DataSetException) { 146 return (DataSetException) exception; 147 } else { 148 return new DataSetException(message, exception); 149 } 150 } 151 152 // ////////////////////////////////////////////////////////////////////////// 153 // EntityResolver interface 154 155 public InputSource resolveEntity(String publicId, String systemId) 156 throws SAXException { 157 logger.debug("resolveEntity(publicId={}, systemId={}) - start", 158 publicId, systemId); 159 160 InputStream in = getClass().getClassLoader().getResourceAsStream( 161 "org/dbunit/dataset/xml/dataset.dtd"); 162 return (new InputSource(in)); 163 } 164 165 // ////////////////////////////////////////////////////////////////////// 166 // ContentHandler interface 167 168 public void startElement(String uri, String localName, String qName, 169 Attributes attributes) throws SAXException { 170 if (logger.isDebugEnabled()) { 171 logger.debug( 172 "startElement(uri={}, localName={}, qName={}, attributes={}) - start", 173 new Object[] { uri, localName, qName, attributes }); 174 } 175 176 try { 177 // dataset 178 if (qName.equals(DATASET)) { 179 _consumer.startDataSet(); 180 return; 181 } 182 183 // table 184 if (qName.equals(TABLE)) { 185 _activeTableName = attributes.getValue(NAME); 186 _activeColumnNames = new LinkedList<String>(); 187 return; 188 } 189 190 // column 191 if (qName.equals(COLUMN)) { 192 _activeCharacters = new StringBuffer(); 193 return; 194 } 195 196 // row 197 if (qName.equals(ROW)) { 198 // End of metadata at first row 199 if (_activeColumnNames != null) { 200 _activeMetaData = createMetaData(_activeTableName, 201 _activeColumnNames); 202 _consumer.startTable(_activeMetaData); 203 204 __activeColumnNames = _activeColumnNames; 205 _activeColumnNames = null; 206 } 207 208 // _activeRowValues = new LinkedList(); 209 _activeRowValues = new HashMap<String, String>(); 210 return; 211 } 212 213 // value 214 if (qName.equals(VALUE)) { 215 _activeColumnName = attributes.getValue(COLUMN_ATTR_NAME); 216 _activeCharacters = new StringBuffer(); 217 return; 218 } 219 220 // // null 221 // if (qName.equals(NULL)) { 222 // _activeRowValues.add(null); 223 // return; 224 // } 225 // 226 // // none 227 // if (qName.equals(NONE)) { 228 // _activeRowValues.add(ITable.NO_VALUE); 229 // return; 230 // } 231 } catch (DataSetException e) { 232 throw new SAXException(e); 233 } 234 } 235 236 public void endElement(String uri, String localName, String qName) 237 throws SAXException { 238 if (logger.isDebugEnabled()) { 239 logger.debug("endElement(uri={}, localName={}, qName={}) - start", 240 new Object[] { uri, localName, qName }); 241 } 242 243 try { 244 // dataset 245 if (qName.equals(DATASET)) { 246 _consumer.endDataSet(); 247 return; 248 } 249 250 // table 251 if (qName.equals(TABLE)) { 252 __activeColumnNames = null; 253 // End of metadata 254 if (_activeColumnNames != null) { 255 _activeMetaData = createMetaData(_activeTableName, 256 _activeColumnNames); 257 _consumer.startTable(_activeMetaData); 258 _activeColumnNames = null; 259 } 260 261 _consumer.endTable(); 262 _activeTableName = null; 263 _activeMetaData = null; 264 return; 265 } 266 267 // column 268 if (qName.equals(COLUMN)) { 269 _activeColumnNames.add(_activeCharacters.toString()); 270 _activeCharacters = null; 271 return; 272 } 273 274 // row 275 if (qName.equals(ROW)) { 276 final int length = __activeColumnNames.size(); 277 Object[] values = new Object[length]; 278 // for (int i = 0; i < values.length; i++) { 279 // values[i] = (i >= _activeRowValues.size()) ? ITable.NO_VALUE 280 // : _activeRowValues.get(i); 281 // } 282 for(int i = 0;i < length; i ++){ 283 values[i] = _activeRowValues.get(__activeColumnNames.get(i)); 284 } 285 _consumer.row(values); 286 _activeRowValues = null; 287 return; 288 } 289 290 // value 291 if (qName.equals(VALUE)) { 292 _activeRowValues.put(_activeColumnName, _activeCharacters.toString()); 293 _activeColumnName = null; 294 _activeCharacters = null; 295 return; 296 } 297 298 // // null 299 // if (qName.equals(NULL)) { 300 // // Nothing to do, already processed in startElement() 301 // return; 302 // } 303 // 304 // // none 305 // if (qName.equals(NONE)) { 306 // // Nothing to do, already processed in startElement() 307 // return; 308 // } 309 } catch (DataSetException e) { 310 throw new SAXException(e); 311 } 312 } 313 314 public void characters(char ch[], int start, int length) 315 throws SAXException { 316 if (_activeCharacters != null) { 317 _activeCharacters.append(ch, start, length); 318 } 319 } 320 321 public void error(SAXParseException e) throws SAXException { 322 throw e; 323 } 324 325 }
原来value是读到一个list里的,然后和column比较,少的部分用空的Object填,我改成先把value根据name属性放到一个map中,最后再放到一个数组,map中没有的值就是null。
CustomXmlDataSet:
1 import java.io.InputStream; 2 import java.io.Reader; 3 4 import org.dbunit.dataset.CachedDataSet; 5 import org.dbunit.dataset.DataSetException; 6 import org.xml.sax.InputSource; 7 8 /** 9 * @author hlw 10 * 11 */ 12 public class CustomXmlDataSet extends CachedDataSet { 13 14 /** 15 * Creates an XmlDataSet with the specified xml reader. 16 */ 17 public CustomXmlDataSet(Reader reader) throws DataSetException { 18 super(new CustomXmlProducer(new InputSource(reader))); 19 } 20 21 /** 22 * Creates an XmlDataSet with the specified xml input stream. 23 */ 24 public CustomXmlDataSet(InputStream in) throws DataSetException { 25 super(new CustomXmlProducer(new InputSource(in))); 26 } 27 28 }
这个类就是把默认的super(new XmlProducer(...))改成super(new CustomXmlProducer(...))。
然后在使用的时候
InputStream input = resourceLoader.getResource(xmlPath) .getInputStream(); IDataSet dataset = new CustomDataSet(input);
还是和原来的用法一样。
另外如果测试数据有相互的外键引用的话,会插不进去,这时可以把数据库的约束检查关掉,对于mysql可以在connection url后面加上sessionVariables=FOREIGN_KEY_CHECKS=0
如jdbc.url=jdbc\:mysql\://localhost\:3306/test?sessionVariables=FOREIGN_KEY_CHECKS=0