다음 3가지 Writer가 있으며, Database의 영속성과 관련해서는 항상 Flush를 해줘야한다. Writer가 받은 모든 Item이 처리 된 후에 Spring Batch는 현재 트랜잭션을 커밋한다.
JdbcBatchItemWriter
ORM을 사용하지 않는 경우에는 대부분 JdbcBatchItemWriter를 사용한다.
JdbcBatchItemWriter는 JdbcTemplate을 사용하며, JDBC의 Batch 기능을 사용해 한번에 DB로 전달하여 DB내부에서 쿼리가 실행되도록 한다. 어플리케이션과 데이터베이스 간에 데이터를 주고 받는 회수를 최소화하여 성능향상을 할 수 있도록 하기 위해서다.
publicvoidwrite(finalList<? extends T> items) throws Exception {if (!items.isEmpty()) {if (logger.isDebugEnabled()) {logger.debug("Executing batch with "+items.size() +" items."); }int[] updateCounts;int value;if (!this.usingNamedParameters) { updateCounts = (int[])this.namedParameterJdbcTemplate.getJdbcOperations().execute(this.sql,newPreparedStatementCallback<int[]>() {publicint[] doInPreparedStatement(PreparedStatement ps) throwsSQLException,DataAccessException {Iterator var2 =items.iterator();while(var2.hasNext()) {T item =var2.next();JdbcBatchItemWriter.this.itemPreparedStatementSetter.setValues(item, ps);ps.addBatch(); }returnps.executeBatch(); } }); } elseif (items.get(0) instanceof Map &&this.itemSqlParameterSourceProvider==null) { updateCounts =this.namedParameterJdbcTemplate.batchUpdate(this.sql, (Map[])items.toArray(newMap[items.size()])); } else {SqlParameterSource[] batchArgs =newSqlParameterSource[items.size()]; value =0;Object item;for(Iterator var5 =items.iterator(); var5.hasNext(); batchArgs[value++] =this.itemSqlParameterSourceProvider.createSqlParameterSource(item)) { item =var5.next(); } updateCounts =this.namedParameterJdbcTemplate.batchUpdate(this.sql, batchArgs); }if (this.assertUpdates) {for(int i =0; i <updateCounts.length; ++i) { value = updateCounts[i];if (value ==0) {thrownewEmptyResultDataAccessException("Item "+ i +" of "+updateCounts.length+" did not update any rows: ["+items.get(i) +"]",1); } } } } }
이때 write()메서드를 보면 SQL문을 한번씩 호출하는 것이 아닌 batchUpdate로 데이터를 청크 단위로 일괄처리하는 것을 볼 수 있다. 이렇게 하면 실행 성능을 크게 향상 시킬 수 있으며, 데이터 변경 실행을 트랜잭션 내에서 할 수 있다.
Property
Parameter Type
Default
설명
assertUpdates
boolean
true
true이면 모든 아이템이 삽입이나 수정되었는지 검증한다.
즉, 적어도 하나의 항목이 행을 업데이트 하거나 삭제하지 않을 경우 예외(EmptyResultDataAccessException)를 throw할지 설정한다.
dataSource
DataSource
null(필수)
필요한 데이터베이스에 대한 접근 제공
sql
String
null(필수)
각 아이템당 수행할 SQL
itemPreparedStatementSetter
ItemPreparedStatementSetter
null
표준 PreparedState가 제공된다면(파라미터 위치에 ?사용), 이 클래스를 사용해 파라미터 값을 채움
itemSqlParameterSourceProvider
ItemSqlParameterSourceProvider
null
제공된 SQL에 네임드 파라미터가 사용된다면, 이 클래스를 사용해 파라미터 값 채움
simpleJdbcTemplate
SimpleJdbcTemplate
null
SimpleJdbcOperations 인터페이스의 구현체를 주입 가능
afterPropertiesSet
각각 Writer들이 실행되기 위해 필요한 필수 값들이 제대로 세팅되어 있는지 확인
JdbcBatchItemWriterBuilder
JdbcBatchItemWriterBuilder는 다음 3가지 설정 값을 갖고 있다.
Property
Parameter Type
Default
설명
assertUpdates
boolean
true
true이면 모든 아이템이 삽입이나 수정되었는지 검증한다.
즉, 적어도 하나의 항목이 행을 업데이트 하거나 삭제하지 않을 경우 예외(EmptyResultDataAccessException)를 throw할지 설정한다.
이 외에 afterPropertiesSet()메서드를 추가로 알고 있으면 좋다. 이 메서드는 InitalizingBean 인터페이스에서 갖고 있으며, ItemWriter 구현체들은 모두 InitializingBean 인터페이스를 구현하고 있다.
publicvoidafterPropertiesSet() {Assert.notNull(this.namedParameterJdbcTemplate,"A DataSource or a NamedParameterJdbcTemplate is required.");Assert.notNull(this.sql,"An SQL statement is required.");List<String> namedParameters =newArrayList();this.parameterCount=JdbcParameterUtils.countParameterPlaceholders(this.sql, namedParameters);if (namedParameters.size() >0) {if (this.parameterCount!=namedParameters.size()) {thrownewInvalidDataAccessApiUsageException("You can't use both named parameters and classic \"?\" placeholders: "+this.sql); }this.usingNamedParameters=true; }if (!this.usingNamedParameters) {Assert.notNull(this.itemPreparedStatementSetter,"Using SQL statement with '?' placeholders requires an ItemPreparedStatementSetter"); } }
이 메서드는 각각 Writer들이 실행되기 위해 필요한 필수 값들이 제대로 세팅되어 있는지 확인한다. Writer 생성 후 해당 메서드를 실행하면 어느 값이 누락되었는지 알 수 있어서 많이 사용하는 옵션이다.
publicclassHibernateItemWriter<T> implementsItemWriter<T>,InitializingBean { @Overridepublicvoidwrite(List<?extendsT> items) {doWrite(sessionFactory, items);sessionFactory.getCurrentSession().flush();if(clearSession) {sessionFactory.getCurrentSession().clear(); } } /** * Do perform the actual write operation using Hibernate's API. * This can be overridden in a subclass if necessary. * * @param sessionFactory Hibernate SessionFactory to be used * @param items the list of items to use for the write */protectedvoiddoWrite(SessionFactory sessionFactory,List<?extendsT> items) {if (logger.isDebugEnabled()) {logger.debug("Writing to Hibernate with "+items.size()+" items."); }Session currentSession =sessionFactory.getCurrentSession();if (!items.isEmpty()) {long saveOrUpdateCount =0;for (T item : items) {if (!currentSession.contains(item)) {currentSession.saveOrUpdate(item); saveOrUpdateCount++; } }if (logger.isDebugEnabled()) {logger.debug(saveOrUpdateCount +" entities saved/updated.");logger.debug((items.size() - saveOrUpdateCount)+" entities found in session."); } } }
HibernateItemWriter에서 각 아이템에 대해 session.saveOrUpdate 메서드를 호출하며, 모든 아이템이 저장되거나 수정되면 flush 메서드를 통해 모든 변경 사항을 한번에 실행한다.