Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions src/main/java/org/mybatis/spring/batch/MyBatisCursorItemReader.java
Original file line number Diff line number Diff line change
@@ -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<T> extends AbstractItemCountingItemStreamItemReader<T> implements InitializingBean {

private String queryId;

private SqlSessionFactory sqlSessionFactory;
private SqlSession sqlSession;

private Map<String, Object> parameterValues;

private CursorList<T> cursorList;
private Iterator<T> 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<String, Object> parameters = new HashMap<String, Object>();
if (parameterValues != null) {
parameters.putAll(parameterValues);
}

sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
List<T> 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<T>) 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<String, Object> parameterValues) {
this.parameterValues = parameterValues;
}
}
103 changes: 88 additions & 15 deletions src/test/java/org/mybatis/spring/batch/SpringBatchTest.java
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<Employee> reader;
@Qualifier("pagingNoNestedItemReader")
private MyBatisPagingItemReader<Employee> pagingNoNestedItemReader;

@Autowired
@Qualifier("pagingNestedItemReader")
private MyBatisPagingItemReader<Employee> pagingNestedItemReader;

@Autowired
@Qualifier("cursorNoNestedItemReader")
private MyBatisCursorItemReader<Employee> cursorNoNestedItemReader;

@Autowired
@Qualifier("cursorNestedItemReader")
private MyBatisCursorItemReader<Employee> cursorNestedItemReader;

@Autowired
private MyBatisBatchItemWriter<Employee> writer;
Expand All @@ -46,16 +59,76 @@ public class SpringBatchTest {

@Test
@Transactional
public void shouldDuplicateSalaryOfAllEmployees() throws UnexpectedInputException, ParseException, Exception {
public void shouldDuplicateSalaryOfAllEmployees() throws Exception {
List<Employee> employees = new ArrayList<Employee>();
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<Employee> employees = new ArrayList<Employee>();
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<Employee> employees = new ArrayList<Employee>();
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<Employee> employees = new ArrayList<Employee>();
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();
}
}
}
21 changes: 19 additions & 2 deletions src/test/java/org/mybatis/spring/batch/applicationContext.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,26 @@
</bean>

<!-- item reader -->
<bean id="reader" class="org.mybatis.spring.batch.MyBatisPagingItemReader">
<bean id="pagingNoNestedItemReader" class="org.mybatis.spring.batch.MyBatisPagingItemReader">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
<property name="queryId" value="getEmployee" />
<property name="queryId" value="getEmployeeNoNestedPaging" />
<property name="pageSize" value="5"/>
</bean>

<bean id="pagingNestedItemReader" class="org.mybatis.spring.batch.MyBatisPagingItemReader">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
<property name="queryId" value="getEmployeeNestedPaging" />
<property name="pageSize" value="5"/>
</bean>

<bean id="cursorNoNestedItemReader" class="org.mybatis.spring.batch.MyBatisCursorItemReader">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
<property name="queryId" value="getEmployeeNoNestedCursor" />
</bean>

<bean id="cursorNestedItemReader" class="org.mybatis.spring.batch.MyBatisCursorItemReader">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
<property name="queryId" value="getEmployeeNestedCursor" />
</bean>

<bean id="writer" class="org.mybatis.spring.batch.MyBatisBatchItemWriter">
Expand Down
41 changes: 34 additions & 7 deletions src/test/java/org/mybatis/spring/batch/dao/EmployeeMapper.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
Copyright 2010-2012 the original author or authors.

Expand All @@ -23,17 +23,44 @@
-->
<mapper namespace="org.mybatis.spring.batch.dao.UserDao">

<select id="getEmployee" resultType="org.mybatis.spring.batch.domain.Employee">
select * from employees
<select id="getEmployeeNoNestedPaging" resultType="org.mybatis.spring.batch.domain.Employee">
select distinct id,name,salary from employees limit #{_pagesize} offset #{_skiprows}
</select>

<update id="updateEmployee" parameterType="org.mybatis.spring.batch.domain.Employee">
<select id="getEmployeeNestedPaging" resultMap="results">
select id,name,salary,skill from employees order by id limit #{_pagesize} offset #{_skiprows}
</select>

<select id="getEmployeeNoNestedCursor" resultType="org.mybatis.spring.batch.domain.Employee" fetchType="CURSOR"
resultOrdered="true">
select distinct id,name,salary from employees order by id
</select>

<select id="getEmployeeNestedCursor" resultMap="results" fetchType="CURSOR" resultOrdered="true">
select id,name,salary,skill from employees order by id
</select>

<resultMap id="results" type="org.mybatis.spring.batch.domain.Employee">
<id column="id" property="id" />
<result property="name" column="name" />
<result property="salary" column="salary" />
<collection property="skills" ofType="string">
<result column="skill" />
</collection>
</resultMap>

<update id="updateEmployee" parameterType="org.mybatis.spring.batch.domain.Employee">
update employees set salary=#{salary} where id=#{id}
</update>

<select id="check" resultType="int">
select sum(salary) from employees
<select id="checkSalarySum" resultType="int">
select sum(salary) from (select distinct id,salary from employees)
</select>

<select id="checkEmployeeCount" resultType="int">
select count(distinct id)from employees
</select>



</mapper>
4 changes: 2 additions & 2 deletions src/test/java/org/mybatis/spring/batch/db/database-schema.sql
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
);
14 changes: 9 additions & 5 deletions src/test/java/org/mybatis/spring/batch/db/database-test-data.sql
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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');
Loading