使用Spring Batch结合iReport批量生成xls
、pdf
、xlsx
、docx
、pptx
文件。数据源来自之前的MySQL数据库。
报表样式
从数据库生成.jasper
文件见这篇。
程序
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.lzh</groupId>
<artifactId>testSpringBatch</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!--[1]Spring Batch的依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.batch/spring-batch-core -->
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-core</artifactId>
<version>3.0.7.RELEASE</version>
</dependency>
<!--[2]iReport操作的依赖-->
<!-- https://mvnrepository.com/artifact/org.codehaus.groovy/groovy-all -->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.lowagie/itext -->
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.1.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.sf.jasperreports/jasperreports -->
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>5.6.0</version>
</dependency>
<!--[3]MySQL-8的驱动-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<!--[4]Office读写依赖-->
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
</dependency>
<!--[5]iTestAsian字体 手动引入-->
</dependencies>
</project>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--jobLauncher负责batch的启动工作-->
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<!--注入jobRepository-->
<property name="jobRepository" ref="jobRepository"/>
</bean>
<!--jobRepository负责job的整个运行过程中的CRUD操作-->
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"/>
<!--transactionManager负责事务的管理操作-->
<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager"/>
</beans>
batch.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:batch="http://www.springframework.org/schema/batch"
xmlns:http="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch.xsd">
<!--导入前面的Spring配置文件-->
<import resource="applicationContext.xml"/>
<!--========================================Job========================================================-->
<batch:job id="iReportJob">
<batch:step id="step-xls" next="step-pdf">
<batch:tasklet transaction-manager="transactionManager">
<batch:chunk reader="myJasperReader" processor="myJasperPrintProcessor-xls" writer="myBAOSWriter-xls" commit-interval="1"/>
</batch:tasklet>
</batch:step>
<batch:step id="step-pdf" next="step-xlsx">
<batch:tasklet transaction-manager="transactionManager">
<batch:chunk reader="myJasperReader" processor="myJasperPrintProcessor-pdf" writer="myBAOSWriter-pdf" commit-interval="1"/>
</batch:tasklet>
</batch:step>
<batch:step id="step-xlsx" next="step-docx">
<batch:tasklet transaction-manager="transactionManager">
<batch:chunk reader="myJasperReader" processor="myJasperPrintProcessor-xlsx" writer="myBAOSWriter-xlsx" commit-interval="1"/>
</batch:tasklet>
</batch:step>
<batch:step id="step-docx" next="step-pptx">
<batch:tasklet transaction-manager="transactionManager">
<batch:chunk reader="myJasperReader" processor="myJasperPrintProcessor-docx" writer="myBAOSWriter-docx" commit-interval="1"/>
</batch:tasklet>
</batch:step>
<batch:step id="step-pptx">
<batch:tasklet transaction-manager="transactionManager">
<batch:chunk reader="myJasperReader" processor="myJasperPrintProcessor-pptx" writer="myBAOSWriter-pptx" commit-interval="1"/>
</batch:tasklet>
</batch:step>
</batch:job>
<!--========================================Reader======================================================-->
<bean id="myJasperReader" class="org.lzh.reader.MyJasperReader" scope="step">
<property name="driven" value="com.mysql.cj.jdbc.Driver"/>
<property name="url">
<list>
<value>jdbc:mysql://localhost:3306/testdb?useSSL=false</value>
<value>serverTimezone=GMT%2B8</value>
</list>
</property>
<property name="usr" value="lzh"/>
<property name="pwd" value="3838438"/>
<property name="jasperFilePath" value="D:/WorkSpace/iReport/fromMySQL.jasper"/>
<property name="parameters">
<map>
<entry key="author" value="刘知昊"/>
</map>
</property>
</bean>
<!--=========================================Processor==================================================-->
<bean id="myJasperPrintProcessor-xls" class="org.lzh.processor.MyJasperPrintProcessor" scope="step">
<property name="type" value="xls"/>
</bean>
<bean id="myJasperPrintProcessor-pdf" class="org.lzh.processor.MyJasperPrintProcessor" scope="step">
<property name="type" value="pdf"/>
</bean>
<bean id="myJasperPrintProcessor-xlsx" class="org.lzh.processor.MyJasperPrintProcessor" scope="step">
<property name="type" value="xlsx"/>
</bean>
<bean id="myJasperPrintProcessor-docx" class="org.lzh.processor.MyJasperPrintProcessor" scope="step">
<property name="type" value="docx"/>
</bean>
<bean id="myJasperPrintProcessor-pptx" class="org.lzh.processor.MyJasperPrintProcessor" scope="step">
<property name="type" value="pptx"/>
</bean>
<!--==========================================Writer====================================================-->
<bean id="myBAOSWriter-xls" class="org.lzh.writer.MyBAOSWriter" scope="step">
<property name="suffix" value=".xls"/>
</bean>
<bean id="myBAOSWriter-pdf" class="org.lzh.writer.MyBAOSWriter" scope="step">
<property name="suffix" value=".pdf"/>
</bean>
<bean id="myBAOSWriter-xlsx" class="org.lzh.writer.MyBAOSWriter" scope="step">
<property name="suffix" value=".xlsx"/>
</bean>
<bean id="myBAOSWriter-docx" class="org.lzh.writer.MyBAOSWriter" scope="step">
<property name="suffix" value=".docx"/>
</bean>
<bean id="myBAOSWriter-pptx" class="org.lzh.writer.MyBAOSWriter" scope="step">
<property name="suffix" value=".pptx"/>
</bean>
</beans>
JdbcConnector类
package org.lzh.connector;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
//对JDBC做简单封装
public class JdbcConnector {
private static Connection connection = null;
public static Connection link(String driven,String url, String usr, String pwd) {
try {
Class.forName(driven);
connection = DriverManager.getConnection(url, usr, pwd);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return connection;
}
public static void close() {
if (null != connection) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
MyJasperReader类
package org.lzh.reader;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import org.lzh.connector.JdbcConnector;
import org.springframework.batch.item.ItemReader;
import java.sql.Connection;
import java.util.Map;
//读取jasper文件,与参数、数据源对象一并封装成JasperPrint总资源对象
public class MyJasperReader implements ItemReader<JasperPrint> {
//由Spring注入
private String driven;//驱动字符串
private String[] url;//连接字符串
private String usr;//连接账户
private String pwd;//密码
private String jasperFilePath;//编译后的jasper文件位置
private Map<String, Object> parameters;//用来设置参数的Map
//用来指示是否应当结束,不知道为什么一直重复执行step,只能用此下策
private boolean isEnd = false;
//用&来连接url
private String linkUrl() {
return String.join("&", url);
}
@Override
public JasperPrint read() throws Exception {
System.out.println("---Reader开始了---");
//建立连接,即作为数据源对象(这里写死了,可以考虑加个List字段,然后可以选择从POJO的列表建立数据源)
Connection connection = JdbcConnector.link(driven, linkUrl(), usr, pwd);
//将jasper资源、参数Map、数据源对象整合到一起,形成JasperPrint总资源对象
JasperPrint jasperPrint = JasperFillManager.fillReport(jasperFilePath, parameters, connection);
//如果还没结束,即第一次跑,设置下次结束并正常返回(这时Connection等到Processor中断开)
if (!isEnd){
isEnd=true;
return jasperPrint;
}
//如果已经结束,返回null停止执行
System.out.println("[!]是时候结束step了");
JdbcConnector.close();
return null;
}
public void setDriven(String driven) {
this.driven = driven;
}
public void setUrl(String[] url) {
this.url = url;
}
public void setUsr(String usr) {
this.usr = usr;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public void setJasperFilePath(String jasperFilePath) {
this.jasperFilePath = jasperFilePath;
}
public void setParameters(Map<String, Object> parameters) {
this.parameters = parameters;
}
}
MyJasperPrintProcessor类
package org.lzh.processor;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.export.*;
import net.sf.jasperreports.engine.export.ooxml.JRDocxExporter;
import net.sf.jasperreports.engine.export.ooxml.JRPptxExporter;
import net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter;
import net.sf.jasperreports.export.*;
import org.lzh.connector.JdbcConnector;
import org.springframework.batch.item.ItemProcessor;
import java.io.ByteArrayOutputStream;
//拿到JasperPrint总资源对象,做相应的处理,生成ByteArrayOutputStream用于输出
public class MyJasperPrintProcessor implements ItemProcessor<JasperPrint, ByteArrayOutputStream> {
//由Spring来注入
private String type;//要生成的文件类型
//字节数组输出流,用于返回
private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
@Override
public ByteArrayOutputStream process(JasperPrint jasperPrint) throws Exception {
//类型缺失
if (null == type) {
System.out.println("[x]没有为Processor注入要生成的文件类型");
//注意关闭连接
JdbcConnector.close();
return null;//null则告诉Writer不需再处理
}
//这个try-finally用于在真正执行return前关闭连接
try {
//对于不同的类型做不同的处理
if (type.equals("pdf")) {
/**演示旧的实现方式*/
//PDF输出工具
JRPdfExporter jrPdfExporter = new JRPdfExporter();
//为输出工具组合插件
jrPdfExporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);//总资源对象
jrPdfExporter.setParameter(JRExporterParameter.OUTPUT_STREAM, byteArrayOutputStream);//输出字节数组流
jrPdfExporter.setParameter(JRExporterParameter.CHARACTER_ENCODING, "UTF-8");//编码
//导出,导出结束后会按照参数将结果放在输出字节数组流中
jrPdfExporter.exportReport();
return byteArrayOutputStream;
} else if (type.equals("xls")) {
/**演示新的实现方式,后面一概采用新的实现方式,旧的已经被@deprecate了*/
//Xls输出工具
JRXlsExporter jrXlsExporter = new JRXlsExporter();
//为输出工具组合插件,不再使用被@deprecated的setParameter方法
jrXlsExporter.setExporterInput(new SimpleExporterInput(jasperPrint));//总资源对象
jrXlsExporter.setExporterOutput(new SimpleOutputStreamExporterOutput(byteArrayOutputStream));//输出字节流数组
//XLS配置对象
SimpleXlsReportConfiguration configuration = new SimpleXlsReportConfiguration();
configuration.setOnePagePerSheet(true);//每个工作表仅一页
configuration.setDetectCellType(true);
configuration.setCollapseRowSpan(false);
//把配好的配置对象装载到输出工具上
jrXlsExporter.setConfiguration(configuration);
//导出,导出结束后会按照参数将结果放在输出字节数组流中
jrXlsExporter.exportReport();
return byteArrayOutputStream;
} else if (type.equals("docx")) {
JRDocxExporter jrDocxExporter = new JRDocxExporter();
jrDocxExporter.setExporterInput(new SimpleExporterInput(jasperPrint));
jrDocxExporter.setExporterOutput(new SimpleOutputStreamExporterOutput(byteArrayOutputStream));
SimpleDocxReportConfiguration configuration = new SimpleDocxReportConfiguration();
configuration.setFlexibleRowHeight(true);//随便设置个配置
jrDocxExporter.setConfiguration(configuration);
jrDocxExporter.exportReport();
return byteArrayOutputStream;
} else if (type.equals("pptx")) {
JRPptxExporter jrPptxExporter = new JRPptxExporter();
jrPptxExporter.setExporterInput(new SimpleExporterInput(jasperPrint));
jrPptxExporter.setExporterOutput(new SimpleOutputStreamExporterOutput(byteArrayOutputStream));
//SimplePptxReportConfiguration configuration=new SimplePptxReportConfiguration();
//也可以像这样不设置配置,那就不需要装配这个设置对象到输出工具上,直接输出就好了
jrPptxExporter.exportReport();
return byteArrayOutputStream;
} else if (type.equals("xlsx")) {
JRXlsxExporter jrXlsxExporter = new JRXlsxExporter();
jrXlsxExporter.setExporterInput(new SimpleExporterInput(jasperPrint));
jrXlsxExporter.setExporterOutput(new SimpleOutputStreamExporterOutput(byteArrayOutputStream));
SimpleXlsxExporterConfiguration configuration = new SimpleXlsxExporterConfiguration();
configuration.setKeepWorkbookTemplateSheets(true);
jrXlsxExporter.setConfiguration(configuration);
jrXlsxExporter.exportReport();
return byteArrayOutputStream;
}// FIXME 继续添加其它输出文件类型
//至此是未识别的类型
System.out.println("[x]未能被Processor识别的类型");
return null;
} finally {
System.out.println("[v]关闭连接...");
JdbcConnector.close();
}
}
public void setType(String type) {
this.type = type;
}
}
MyBAOSWriter类
package org.lzh.writer;
import org.springframework.batch.item.ItemWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.List;
//用于输出,可以注入文件后缀使其变成不同功能的Writer
public class MyBAOSWriter implements ItemWriter<ByteArrayOutputStream> {
//由Spring注入
private String suffix;//文件后缀,如".xls"
@Override
public void write(List<? extends ByteArrayOutputStream> list) throws Exception {
if (null == list) {
System.out.println("[x]无可输出");
return;
}
//遍历要输出的每个BAOS
for (ByteArrayOutputStream baos : list) {
//跳过无可输出
if(null==baos)
continue;
//将其变成字节数组
byte[] bytes=baos.toByteArray();
//写出到文件,携带后缀
File file=new File("D:/WorkSpace/iReport/test/lzh"+suffix);
FileOutputStream fileOutputStream=new FileOutputStream(file);
fileOutputStream.write(bytes);
System.out.println("[v]写入"+suffix);
fileOutputStream.close();
}
System.out.println("---本次Writer已工作结束---");
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
Main类
package org.lzh;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
//batch.xml导入了applicationConext.xml,所以只写batch.xml就可以一并加入上下文
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("batch.xml");
//Spring Batch的作业启动器,在applicationContext.xml中配置为了bean
JobLauncher jobLauncher = (JobLauncher) applicationContext.getBean("jobLauncher");
//在batch.xml中配置的一个作业
Job job = (Job) applicationContext.getBean("iReportJob");
try {
//开始执行这个作业,获得处理结果(要运行的job,job参数对象)
JobExecution result = jobLauncher.run(job, new JobParameters());
//输出处理结果看一下
System.out.println("处理结果:" + result.toString());
} catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException | JobRestartException | JobParametersInvalidException e) {
e.printStackTrace();
}
}
}
运行
输出
生成