diff --git a/src/main/java/org/mybatis/spring/batch/MyBatisCursorItemReader.java b/src/main/java/org/mybatis/spring/batch/MyBatisCursorItemReader.java new file mode 100644 index 0000000000..659bbae425 --- /dev/null +++ b/src/main/java/org/mybatis/spring/batch/MyBatisCursorItemReader.java @@ -0,0 +1,126 @@ +/* + * Copyright 2010-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.spring.batch; + +import static org.springframework.util.Assert.notNull; +import static org.springframework.util.ClassUtils.getShortName; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.executor.resultset.CursorList; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader; +import org.springframework.beans.factory.InitializingBean; + +/** + * @author Guillaume Darmont / guillaume@dropinocean.com + */ +public class MyBatisCursorItemReader extends AbstractItemCountingItemStreamItemReader implements InitializingBean { + + private String queryId; + + private SqlSessionFactory sqlSessionFactory; + private SqlSession sqlSession; + + private Map parameterValues; + + private CursorList cursorList; + private Iterator cursorIterator; + + public MyBatisCursorItemReader() { + setName(getShortName(MyBatisCursorItemReader.class)); + } + + @Override + protected T doRead() throws Exception { + T next = null; + if (cursorIterator.hasNext()) { + next = cursorIterator.next(); + } + return next; + } + + @Override + protected void doOpen() throws Exception { + Map parameters = new HashMap(); + if (parameterValues != null) { + parameters.putAll(parameterValues); + } + + sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE); + List list = sqlSession.selectList(queryId, parameters); + if (!(list instanceof CursorList)) { + throw new IllegalStateException( + "MyBatisCursorItemReader can only work with fetchType=\"CURSOR\". Please configure " + queryId + + " correctly."); + } + cursorList = (CursorList) list; + cursorIterator = cursorList.iterator(); + } + + @Override + protected void doClose() throws Exception { + // Ensure that cursorList is closed, even if resultset is partially consumed. + cursorList.closeResultSetAndStatement(); + sqlSession.close(); + cursorIterator = null; + cursorList = null; + } + + /** + * Check mandatory properties. + * + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception { + notNull(sqlSessionFactory); + notNull(queryId); + } + + /** + * Public setter for {@link SqlSessionFactory} for injection purposes. + * + * @param SqlSessionFactory sqlSessionFactory + */ + public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { + this.sqlSessionFactory = sqlSessionFactory; + } + + /** + * Public setter for the statement id identifying the statement in the SqlMap + * configuration file. + * + * @param queryId the id for the statement + */ + public void setQueryId(String queryId) { + this.queryId = queryId; + } + + /** + * The parameter values to be used for the query execution. + * + * @param parameterValues the values keyed by the parameter named used in + * the query string. + */ + public void setParameterValues(Map parameterValues) { + this.parameterValues = parameterValues; + } +} diff --git a/src/test/java/org/mybatis/spring/batch/SpringBatchTest.java b/src/test/java/org/mybatis/spring/batch/SpringBatchTest.java index c92469e962..b7751c9e75 100644 --- a/src/test/java/org/mybatis/spring/batch/SpringBatchTest.java +++ b/src/test/java/org/mybatis/spring/batch/SpringBatchTest.java @@ -1,12 +1,12 @@ /* - * Copyright 2010-2012 the original author or authors.. - * + * Copyright 2010-2012 The MyBatis Team. + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,28 +15,41 @@ */ package org.mybatis.spring.batch; -import static org.junit.Assert.*; - import java.util.ArrayList; import java.util.List; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + import org.apache.ibatis.session.SqlSession; import org.junit.Test; import org.junit.runner.RunWith; import org.mybatis.spring.batch.domain.Employee; -import org.springframework.batch.item.ParseException; -import org.springframework.batch.item.UnexpectedInputException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(locations = { "classpath:org/mybatis/spring/batch/applicationContext.xml" }) +@ContextConfiguration(locations = {"classpath:org/mybatis/spring/batch/applicationContext.xml"}) public class SpringBatchTest { @Autowired - private MyBatisPagingItemReader reader; + @Qualifier("pagingNoNestedItemReader") + private MyBatisPagingItemReader pagingNoNestedItemReader; + + @Autowired + @Qualifier("pagingNestedItemReader") + private MyBatisPagingItemReader pagingNestedItemReader; + + @Autowired + @Qualifier("cursorNoNestedItemReader") + private MyBatisCursorItemReader cursorNoNestedItemReader; + + @Autowired + @Qualifier("cursorNestedItemReader") + private MyBatisCursorItemReader cursorNestedItemReader; @Autowired private MyBatisBatchItemWriter writer; @@ -46,16 +59,76 @@ public class SpringBatchTest { @Test @Transactional - public void shouldDuplicateSalaryOfAllEmployees() throws UnexpectedInputException, ParseException, Exception { + public void shouldDuplicateSalaryOfAllEmployees() throws Exception { List employees = new ArrayList(); - Employee employee = reader.read(); + Employee employee = pagingNoNestedItemReader.read(); while (employee != null) { employee.setSalary(employee.getSalary() * 2); employees.add(employee); - employee = reader.read(); + employee = pagingNoNestedItemReader.read(); } writer.write(employees); - assertEquals(20000, session.selectOne("check")); + assertEquals(20000, session.selectOne("checkSalarySum")); + assertEquals(employees.size(), session.selectOne("checkEmployeeCount")); + } + + @Test + @Transactional + public void checkPagingReadingWithNestedInResultMap() throws Exception { + // This test is here to show that PagingReader can return wrong result in case of nested result maps + List employees = new ArrayList(); + Employee employee = pagingNestedItemReader.read(); + while (employee != null) { + employee.setSalary(employee.getSalary() * 2); + employees.add(employee); + employee = pagingNestedItemReader.read(); + } + writer.write(employees); + + // Assert that we have a WRONG employee count + assertNotEquals(employees.size(), session.selectOne("checkEmployeeCount")); + } + + @Test + @Transactional + public void checkCursorReadingWithoutNestedInResultMap() throws Exception { + cursorNoNestedItemReader.doOpen(); + try { + List employees = new ArrayList(); + Employee employee = cursorNoNestedItemReader.read(); + while (employee != null) { + employee.setSalary(employee.getSalary() * 2); + employees.add(employee); + employee = cursorNoNestedItemReader.read(); + } + writer.write(employees); + + assertEquals(20000, session.selectOne("checkSalarySum")); + assertEquals(employees.size(), session.selectOne("checkEmployeeCount")); + } finally { + cursorNoNestedItemReader.doClose(); + } + } + + @Test + @Transactional + public void checkCursorReadingWithNestedInResultMap() throws Exception { + cursorNestedItemReader.doOpen(); + try { + List employees = new ArrayList(); + Employee employee = cursorNestedItemReader.read(); + while (employee != null) { + employee.setSalary(employee.getSalary() * 2); + employees.add(employee); + employee = cursorNestedItemReader.read(); + } + writer.write(employees); + + assertEquals(20000, session.selectOne("checkSalarySum")); + assertEquals(employees.size(), session.selectOne("checkEmployeeCount")); + } finally { + cursorNestedItemReader.doClose(); + } } } diff --git a/src/test/java/org/mybatis/spring/batch/applicationContext.xml b/src/test/java/org/mybatis/spring/batch/applicationContext.xml index 70e69f4a4c..d8c354e0c8 100644 --- a/src/test/java/org/mybatis/spring/batch/applicationContext.xml +++ b/src/test/java/org/mybatis/spring/batch/applicationContext.xml @@ -60,9 +60,26 @@ - + - + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/mybatis/spring/batch/dao/EmployeeMapper.xml b/src/test/java/org/mybatis/spring/batch/dao/EmployeeMapper.xml index a715f9558a..15234134f4 100644 --- a/src/test/java/org/mybatis/spring/batch/dao/EmployeeMapper.xml +++ b/src/test/java/org/mybatis/spring/batch/dao/EmployeeMapper.xml @@ -1,7 +1,7 @@ + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + select distinct id,name,salary from employees limit #{_pagesize} offset #{_skiprows} - + + + + + + + + + + + + + + + + update employees set salary=#{salary} where id=#{id} - + select sum(salary) from (select distinct id,salary from employees) + + + diff --git a/src/test/java/org/mybatis/spring/batch/db/database-schema.sql b/src/test/java/org/mybatis/spring/batch/db/database-schema.sql index 60111e082e..ab0d0101c7 100644 --- a/src/test/java/org/mybatis/spring/batch/db/database-schema.sql +++ b/src/test/java/org/mybatis/spring/batch/db/database-schema.sql @@ -1,4 +1,4 @@ --- Copyright 2010-2012 the original author or authors. +-- Copyright 2010-2012 The myBatis Team -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. @@ -18,5 +18,5 @@ create table employees ( id integer not null, name varchar(80) not null, salary integer not null, - constraint pk_employee primary key (id) + skill varchar(80) not null ); diff --git a/src/test/java/org/mybatis/spring/batch/db/database-test-data.sql b/src/test/java/org/mybatis/spring/batch/db/database-test-data.sql index 6e9fb550df..1b7f7f30f2 100644 --- a/src/test/java/org/mybatis/spring/batch/db/database-test-data.sql +++ b/src/test/java/org/mybatis/spring/batch/db/database-test-data.sql @@ -1,4 +1,4 @@ --- Copyright 2010-2012 the original author or authors. +-- Copyright 2010-2012 The myBatis Team -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. @@ -14,7 +14,11 @@ -- version: $Id: db.sql 2398 2010-08-29 15:16:24Z simone.tripodi $ -insert into employees VALUES ( 1, 'Pocoyo' , 1000); -insert into employees VALUES ( 2, 'Pato' , 2000); -insert into employees VALUES ( 3, 'Eli' , 3000); -insert into employees VALUES ( 4, 'Valentina' , 4000); +insert into employees VALUES ( 1, 'Pocoyo' , 1000, 's1'); +insert into employees VALUES ( 1, 'Pocoyo' , 1000, 's2'); +insert into employees VALUES ( 2, 'Pato' , 2000, 's1'); +insert into employees VALUES ( 2, 'Pato' , 2000, 's2'); +insert into employees VALUES ( 3, 'Eli' , 3000, 's1'); +insert into employees VALUES ( 3, 'Eli' , 3000, 's2'); +insert into employees VALUES ( 3, 'Eli' , 3000, 's3'); +insert into employees VALUES ( 4, 'Valentina' , 4000, 's1'); diff --git a/src/test/java/org/mybatis/spring/batch/domain/Employee.java b/src/test/java/org/mybatis/spring/batch/domain/Employee.java index 7a9d269cdd..2ba417c944 100644 --- a/src/test/java/org/mybatis/spring/batch/domain/Employee.java +++ b/src/test/java/org/mybatis/spring/batch/domain/Employee.java @@ -15,11 +15,14 @@ */ package org.mybatis.spring.batch.domain; +import java.util.List; + public class Employee { private int id; private String name; private int salary; + private List skills; public int getId() { return id; @@ -45,4 +48,11 @@ public void setSalary(int salary) { this.salary = salary; } + public List getSkills() { + return skills; + } + + public void setSkills(List skills) { + this.skills = skills; + } }