不想用POI?几行代码完成Excel导出导入

Octopus

Octopus 是一个简单的java excel导入导出工具。目的是不用接触Apache POI的API就可以完成简单的Excel导出导入。
同时,可以自定义表格样式,导入检验数据和转换数据

Github地址

不BB,直接上图

从Maven导入

<dependency>
	<groupId>cn.chenhuanming</groupId>
	<artifactId>octopus</artifactId>
	<version>1.1.4</version>
</dependency>

导出Excel

从最简单的例子开始

我们从最简单的例子开始,导出一些地址数据

定义Address

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
    private String city;
    private String detail;
}

用XML文件定义怎么导出

<?xml version="1.0" encoding="UTF-8"?>
<Root class="cn.chenhuanming.octopus.entity.Address">

    <Field name="city" description="City"/>
    <Field name="detail" description="Detail"/>

</Root>

Root标签的class属性,代表我们要导出的类全限定名

一个Field标签代表Excel里的一列数据

name属性值就是Address里的属性名,实际上Octopus调用其getter方法获取值,所以要确保有getter方法

description属性会被用来绘制表头

我们可以开始做最后一件事,编写Java代码

public class AddressExample {
    List<Address> addresses;

    /**
     * preparing testing data
     */
    @Before
    public void prepare() {
        addresses = new ArrayList<>();
        DataFactory df = new DataFactory();
        for (int i = 0; i < df.getNumberBetween(5, 10); i++) {
            addresses.add(new Address(df.getCity(), df.getAddress()));
        }
    }

    @Test
    public void export() throws Exception {

        //导出文件的is
        String rootPath = this.getClass().getClassLoader().getResource("").getPath();
        FileOutputStream os = new FileOutputStream(rootPath + "/address.xlsx");

        //从XML读取配置,建议单例模式
        InputStream is = this.getClass().getClassLoader().getResourceAsStream("address.xml");
        Config config = new XmlConfigFactory(is).getConfig();

        //用Octopus,只需要一行代码
        Octopus.writeOneSheet(os, config, "address", addresses);
    }
}

这是一个完整的单元测试,可以在单测测试路径找到它

自动绘制表头

Octopus支持导出复杂对象时自动绘制表头

这次我们来导出一些公司数据,这里是Company

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Company {
    private String name;
    private Address address;
}

然后我们创建一个 company.xml 配置文件

<Root class="cn.chenhuanming.octopus.entity.Address">


    <Field name="name"
           description="Name"
           color="#ff0000"/>

    <Header name="address" description="Address">
        <Field name="city" description="City"/>
        <Field name="detail" description="Detail"/>
    </Header>

</Root>

我们用Header元素代表要导出Company的一个复杂属性,同时设置字体颜色是红色

Java代码基本跟之前的一样

public class CompanyExample {
    List<Company> companies;

    /**
     * preparing testing data
     */
    @Before
    public void prepare() {
        companies = new ArrayList<>();
        DataFactory df = new DataFactory();
        for (int i = 0; i < df.getNumberBetween(5, 10); i++) {
            companies.add(new Company(df.getBusinessName(), new Address(df.getCity(), df.getAddress())));
        }
    }

    @Test
    public void export() throws Exception {

        //导出文件的is
        String rootPath = this.getClass().getClassLoader().getResource("").getPath();
        FileOutputStream os = new FileOutputStream(rootPath + "/company.xlsx");

        //从XML读取配置,建议单例模式
        InputStream is = this.getClass().getClassLoader().getResourceAsStream("company.xml");
        Config config = new XmlConfigFactory(is).getConfig();

        Octopus.writeOneSheet(os, config, "company", companies);
    }
}

最后是导出的Excel文件

Octopus可以处理更复杂的数据,你可以在cn.chenhuanming.octopus.example.ApplicantExample查看这个更复杂的例子

转换数据

有时你想转换导出的数据。例如,在上一个例子中,我们不想导出整个Address对象,把它当做一个一列数据导出

我们所需要做的只是实现一个Formatter

public class AddressFormatter implements Formatter<Address> {
    @Override
    public String format(Address address) {
        return address.getCity() + "," + address.getDetail();
    }

    @Override
    public Address parse(String str) {
        String[] split = str.split(",");
        if (split.length != 2) {
            return null;
        }
        return new Address(split[0], split[1]);
    }
}

parse方法用于导入Excel,只要关注format方法。这里接受一个Address对象,返回一个字符串。

最后,配置AddressFormatter到XML文件

<Field name="name"
          description="Name"
          color="#ff0000"/>

<Field name="address"
      description="Address"
      formatter="cn.chenhuanming.octopus.formatter.AddressFormatter"/>

最后导出的结果

导入Excel

我们直接拿上一个例子的导出结果来演示导入,共用同一个Config对象,直接编写导入的代码

SheetReader<Company> importData = Octopus.readFirstSheet(fis, config, new DefaultCellPosition(1, 0));

for (Company company : importData) {
    System.out.println(company);
}

在控制台可以看到打印导入结果,可以看到,之前的AddressFormatter也完成了数据的转换工作

Company(name=Graham Motor Services, address=Address(city=Monroe, detail=666 Bonnair Ave))
Company(name=Social Circle Engineering, address=Address(city=Fort Gaines, detail=956 Third Ridge))
Company(name=Enigma Cafe, address=Address(city=Mcdonough, detail=1278 Midway Trail))
Company(name=Hapeville Studios, address=Address(city=Riceboro, detail=823 Tuscarawas Blvd))
Company(name=Thalman Gymnasium, address=Address(city=Ebenezer, detail=1225 Blackwood Avenue))
Company(name=Sparks Pro Services, address=Address(city=Darien, detail=1362 Woodlawn Lane))
Company(name=Toccoa Development, address=Address(city=Ridgeville, detail=1790 Lawn Ave))

导入校验数据

有时候我们对导入的数据有一定的要求,Octopus提供简单的数据校验配置

首先给我们的Company增加一个status属性,只能是 good,badclosed 三个值其中一个,同时name不可以为空,看一下XML配置文件

<?xml version="1.0" encoding="UTF-8"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      class="cn.chenhuanming.octopus.entity.Company">


    <Field name="name"
           description="Name"
           color="#ff0000"
           is-blankable="false"/>

    <Field name="address"
           description="Address"
           formatter="cn.chenhuanming.octopus.formatter.AddressFormatter"
    />

    <Field name="status"
           description="Status"
           options="good|bad|closed"/>
    <!--| split options -->
    
</Root>

这是我们要导入的Excel,可以看到里面有非法数据

看一下怎么编写Java代码

@Test
public void importCheckedData() throws IOException, InvalidFormatException {
    InputStream is = this.getClass().getClassLoader().getResourceAsStream("wrongCompany.xlsx");

    Config config = new XmlConfigFactory(this.getClass().getClassLoader().getResourceAsStream("company3.xml")).getConfig();

    final SheetReader<CheckedData<Company>> sheetReader = Octopus.readFirstSheetWithValidation(is,config,new DefaultCellPosition(1,0));

    for (CheckedData<Company> checkedData : sheetReader) {
        System.out.println(checkedData);
    }
}

这里我们调用Octopus.readFirstSheetWithValidation,获取带校验结果的SheetReader,看一下导入的结果

CheckedData(data=Company(name=Graham Motor Services, address=Address(city=Monroe, detail=666 Bonnair Ave), status=good), exceptions=[])
CheckedData(data=Company(name=Social Circle Engineering, address=Address(city=Fort Gaines, detail=956 Third Ridge), status=null), exceptions=[cn.chenhuanming.octopus.exception.NotAllowValueException])
CheckedData(data=Company(name=null, address=Address(city=Mcdonough, detail=1278 Midway Trail), status=null), exceptions=[cn.chenhuanming.octopus.exception.CanNotBeBlankException, cn.chenhuanming.octopus.exception.NotAllowValueException])

可以看到每一个CheckData有一个data属性和一个exceptions列表。
这个异常列表存放着导入时每一个单元格可能出现的校验错误,异常类型都是ParseException

除了is-blankableoptions,还可以通过regex配置正则表达式检查。当校验错误时,会抛出对应的ParseException子类

  • is-blankable:抛出 CanNotBeBlankException
  • options:抛出 NotAllowValueException
  • regex:抛出 PatternNotMatchException

你通过这些异常来进行跟进一步的处理。如果上面三种校验方式不能满足需求,在Formatterparse抛出自定义的ParseException。Octopus会捕获它们放到exceptions列表中,并自动把单元格位置和你的配置内容塞到ParseException

以上代码都可以在测试路径cn.chenhuanming.octopus.example找到,通过这些例子可以感受下Octopus的魅力

注解

我们推荐使用 xml 方式配置导入导出格式,因为 xml 配置与类不相耦合,相比注解更加灵活。
不过有时使用方可能不太在意灵活性,希望把配置和数据类放在一起,那么可以使用注解版本。
注解与 xml 文件的使用方法类似,主要有 @Sheet,@Formatter,@Header,@Field 这几个。

  • @Sheet 注解在数据类上,可选 formatters 属性, 表示全局转换器
  • @Formatter 作为 @Sheet 的 formatters 属性值,表示一个转换器
  • @Header 注解在数据类的字段上,表示该字段是一个复合字段
  • @Field 注解在数据类的字段上,表示该字段是一个单一字段

注解的的属性取值请参考 xml 文件。下面是一个数据类的注解示例:

@Sheet(formatters = {
        @Formatter(target = BigDecimal.class, format = BigDecimalFormatter.class),
})
public class Applicants {
    @Field(description = "Value", color = "#74f441")
    private int id;
    @Field(description = "Name", fontSize = 20, border = "0,2,0,2", borderColor = ",#4242f4,,#4242f4")
    private String name;
    @Header(description = "Job", headerColor = "#4286f4")
    private Job job;
    @Field(description = "Entry Date", dateFormat = "yyyy-MM-dd")
    private Date entryDate;
    @Field(description = "Working/Leaved", options = "Working|Leaved",
            formatter = cn.chenhuanming.octopus.formatter.WorkingFormatter.class, color = "#42f4b9")
    private boolean working = true;
}

使用方法:

    // 构造方法必须传入一个带有 @Sheet 注解的类
    Config config = new AnnotationConfigFactory(Applicants.class).getConfig();
    // ... 使用 config 就像 xml 的方式一样

Q&A

需要操作Apache POI?

Octopus类可以提供一行代码式的API,让你不用碰Apache POI的API。但是如果你确实需要用到Apache POI,可以先看一下Octopus核心类SheetWriterSheetReader代码。我在设计的时候尽量考虑扩展,并且完全基于接口实现,实在不行可以选择继承重写,属性基本都是protected,或者直接自己实现接口

有建议或者问题?

提Issue或者email我chenhuanming.cn@gmail.com

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值