在說明 Spring Batch 怎麼處理固定長度欄位檔案前,先看一下 Spring Batch 怎麼處理 csv 檔。如下是 csv 檔的內容:
Buterin,24,Anglo-Saxon,Canada 中本聰,47,大和民族,波士頓
FlatFileItemReader<Person> itemReader = new FlatFileItemReader<Person>(); itemReader.setResource(new ClassPathResource("Person.csv")); DefaultLineMapper<Person> lineMapper = new DefaultLineMapper<Person>(); lineMapper.setLineTokenizer(new DelimitedLineTokenizer(){{ setNames(new String[] { "name", "age", "nation", "address" }); }}); lineMapper.setFieldSetMapper(new PersonFieldSetMapper()); itemReader.setLineMapper(lineMapper); itemReader.open(new ExecutionContext()); Person person = null; while ((person = itemReader.read()) != null) { System.out.println(person.toString()); }
- FlatFileItemReader: 這個類別可用來讀取文字檔,當然,csv 檔是文字檔的一種,也用來讀取 csv 檔。它主要依賴兩類別 -- Resource 及 LineMapper,前者為 spring 提供的基礎類別,可以存取檔案或網路資源,這裡使用的 ClassPathResource 會到 classpath 目錄下讀取指定的檔案。
- DefaultLineMapper: spring batch 定義了 LineMapper 介面,並實作多個類別,這些類別是用來將 String 轉換成相對應的 Object,DefaultLineMapper 可用來處理有分隔符號或固定長度欄位的字串。
- DelimitedLineTokenizer: 當要處理的字串為有分隔符號的,就用這個類別,這裡有使用 setNames 傳入欄位名稱,這是方便在 PersonFieldSetMapper (前一篇) 中使用欄位名稱存取各欄位的值,沒有設定欄位名稱,可以用 index,從 0 開始。
Buterin 24Anglo-Saxon Canada 中本聰 47大和民族 波士頓
FlatFileItemReaderitemReader = new FlatFileItemReader (); itemReader.setResource(new ClassPathResource("Person.txt")); DefaultLineMapper lineMapper = new DefaultLineMapper (); FixedLengthTokenizer tokenizer = new FixedLengthTokenizer(); tokenizer.setNames(new String[] { "name", "age", "nation", "address" }); Range range1 = new Range(1, 10); Range range2 = new Range(11, 12); Range range3 = new Range(13, 30); Range range4 = new Range(31, 40); tokenizer.setColumns(new Range[] { range1, range2, range3, range4 }); lineMapper.setLineTokenizer(tokenizer); lineMapper.setFieldSetMapper(new PersonFieldSetMapper()); itemReader.setLineMapper(lineMapper); itemReader.open(new ExecutionContext()); Person person = null; while ((person = itemReader.read()) != null) { System.out.println(person.toString()); }
org.springframework.batch.item.file.FlatFileParseException: Parsing error at line: 2 in resource=[class path resource [Person.txt]], input=[中本聰 47大和民族 波士頓 ] … Caused by: org.springframework.batch.item.file.transform.IncorrectLineLengthException: Line is shorter than max range 40
@Slf4j public class ZhFixedLengthTokenizer extends FixedLengthTokenizer { private Range[] ranges; private int maxRange = 0; boolean open = false; public void setColumns(Range[] columns) { this.ranges = columns; } @Override public List<String> doTokenize(String line) { List<String> tokens = new ArrayList<String>(ranges.length); String token; try { byte[] b = line.getBytes("MS950"); int lineLength = b.length; for (int i = 0; i < ranges.length; i++) { int startPos = ranges[i].getMin() - 1; int endPos = ranges[i].getMax(); if (lineLength >= endPos) { token = getZhString(b, startPos, endPos); } else if (lineLength >= startPos) { token = getZhString(b, startPos, lineLength); } else { token = ""; } tokens.add(token); } } catch (UnsupportedEncodingException e) { log.error(e.getMessage(), e); } return tokens; } private String getZhString(byte[] b, int startPos, int endPos) throws UnsupportedEncodingException { String token; byte[] subB = Arrays.copyOfRange(b, startPos, endPos); token = new String(subB, "MS950"); return token; } }
這個類別繼承了 FixedLengthTokenizer,然後覆寫其中的 doTokenize,把切 token 時的字串編碼改為 MS950,這樣就可以得到正確結果了! 這些程式碼基本上是從原本的 FixedLengthTokenizer 裡 copy 過來改寫的,然後把 tokenizer 改用這個類別就可以了,如下:
FixedLengthTokenizer tokenizer = new ZhFixedLengthTokenizer();