到处数据如果是成条的,而且是批量处理的情况下,我们可以通过对数据模型使用注解,来解决这种问题。
环境搭建
<!-- excel -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<!-- word -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.17</version>
</dependency>
<!-- xlsx -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
<!-- xlsx 依赖这个包 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.17</version>
</dependency>
实现思路
在数据模型对象下,添加一个注解类,说明抽出的字段在excel中所对应的表头,然后通过工具类,通过反射,获取每个字段所对应的标题,同时设定每个字段对应的数据
POI自定义注解类(HFFSAlias.java)
HFFSAlias.java
这个注解类是基于file字段的,用于说明字段在xls中的中文名称
package com.yellowcong.utils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** * 通过注解来操作别名,判断的是字段上的别名 * 通过这个注解类和我们的PoiUtils 可以很好的解决文档导出的问题 * * @author yellowcong * @date 2016年1月7日 */
@Target({ java.lang.annotation.ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface HFFSAlias{
//设定别名,通过value 这个函数,就不需要写参数了
//@HFFSAlias("xx") 就可以了
public String value() default "";
}
模型类对象(User.java)
这个模型对象,使用的是Hibernate的框架,通过Hibernate做ORM框架,我们只需要调用自定义的
HFFSAlias.java
这个annotation,就可以设定标题了
package com.yellowcong.model;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.hibernate.annotations.Index;
import org.hibernate.validator.constraints.Email;
import com.yellowcong.utils.HFFSAlias;
/** * yellowcong 的新网站,以后也会一直建立下去 * * @author yellowcong * * * QQ * 新浪微博借口 * 微信接口 数据推送 * github 接口 * * * 用户登录的 email * 用户登录的手机号 * 用户的名称 * 必须是唯一的 * * * 网名和头像随机生成,如果用户不填写的情况下 * * * 手机和email登录 * 三方登录 * */
@Entity
@Table(name="ycg_user")
public class User {
@HFFSAlias("编号")
private int id;
//用户名称
@HFFSAlias("用户名称")
private String username;
//别名
//别名不能存在邮箱@ 不然会导致问题,我们需要做js验证
@HFFSAlias("别名")
private String nickname;
//密码
// @HFFSAlias("密码")
private String password;
//创建日期
@HFFSAlias("创建日期")
private Date createDate;
//用户头像
@HFFSAlias("用户头像")
private String imgUrl;
//用户的邮箱地址
@HFFSAlias("邮箱")
private String email;
//电话
@HFFSAlias("电话号")
private String phone;
//是否邮箱激活
// 0 表示未激活
// 1 表示激活
@HFFSAlias("是否激活")
private int isActive;
public User() {
super();
// TODO Auto-generated constructor stub
}
//用于验证用户是否登录
public User(int id) {
super();
this.id = id;
}
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Index(name="username")
@Column(name="username",length=16)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
//我们不需要设定我们的nickname 为唯一的
@Index(name="nickname")
@Column(name="nickname",length=16,unique=false)
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
@Column(length=32)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Column(name="create_date")
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
@Column(name="img_url")
public String getImgUrl() {
return imgUrl;
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}
@Email
@Column(unique=true,length=64)
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Column(name="phone",length=11,unique=true)
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Column(name="is_active",length=1)
public int getIsActive() {
return isActive;
}
public void setIsActive(int isActive) {
this.isActive = isActive;
}
}
工具类
工具类中,主要可供使用的是将获取的
List<?>
数据转化为Excel文档,同时也可以 将转化的Excel文档转化为List<?>
,这几个方法都有一个特点,他们是有表头,而且还有一点缺点,对于时间类型需要指定,不然有问题,同时逆向工程中,读取的表的数据 少一行
package com.yellowcong.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
/** * 报表工具包,用来操作报表中的数据 * @author yellowcong * @date 2016年1月6日 * 依赖 poi-2.5.1.jar * beanutil * * 重要的方法 * 创建xls文件 * * 基于annotation * 类对象需要使用@HHFSAlias 注解 给字段设定别名 * createHSSFByAnnotation(List<?>) * * 自己配置好映射关系,然后传 List<?>对象 * createHSSF(LinkedHashMap<String, String>, List<?>) * * 逆向生成 java对象 * 类对象需要使用@HHFSAlias 注解 给字段设定别名 * reverseHSSFByAnnotation(File, Class<?>) * * 对于文档直接使用,使用String[]来配置我们的 映射关系,boolena 设定是否有表头 * reverseHSSF(File, String[], Class<?>, boolean) * * 对于有表头的可以使用这个,也可以使用上一个,上一个的效率高一点 * reverseHSSF(File, Map<String, String>, Class<?>) */
public class PoiUtils {
private PoiUtils(){}
/** * 将HSSFWorkbook 对象转化为 文件 * @param wb HSSFWorkbook 对象 * @param file 文件 */
public static void copyHSSFToFile(HSSFWorkbook wb,File file){
FileOutputStream out = null;
try {
out = new FileOutputStream(file);
} catch (Exception e) {
// TODO: handle exception
}finally{
try {
if(wb != null){
wb.write(out);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
if(out != null){
out.close();
}
} catch (Exception e2) {
// TODO: handle exception
}
}
}
}
/** * 获取一个不带有输入流的HSSFWorkbook 对象 * @return */
public static HSSFWorkbook getHSSFWorkbook(){
return getHSSFWorkbook(null);
}
/** * 获取我们的一个HSSFWorkbook * @return */
public static HSSFWorkbook getHSSFWorkbook(File file){
HSSFWorkbook workbook= null;
try {
if(file != null){
workbook = new HSSFWorkbook(new FileInputStream(file));
}else{
workbook = new HSSFWorkbook();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return workbook;
}
//判断数据
/** * 通过 结合注解,来生成我们的poi文档操作 * 通过注解调用的时候,我们的类中必须要有注解写出,如果没有就创建不了 * 需要结合@HFFSAlias 这个类 * @param data 我们的数据集合 * @return */
public static HSSFWorkbook createHSSFByAnnotation(List<?> data) {
LinkedHashMap<String, String> title = null;
//获取到我们的title数据
if(data != null && data.size() >0){
title = new LinkedHashMap<String, String>();
/*Field [] fields = data.get(0).getClass().getDeclaredFields(); for(Field field:fields){ HFFSAlias alias = field.getAnnotation(HFFSAlias.class); if(alias != null){ title.put(field.getName(), alias.value()); } }*/
title = PoiUtils.getAnnotationAlias(data.get(0).getClass());
//然后调用已经存在的方法来生成表
return PoiUtils.createHSSF(title, data);
}
return null;
}
/** * 逆向生成,我们需要的数据,将xls中的数据转化为字符串数据 * @param clazz 映射的类对象 * @param file xls文件对象 * @return */
public static List<?> reverseHSSFByAnnotation(File file,Class<?> clazz ){
//获取field中的信息
return PoiUtils.reverseHSSF(file, PoiUtils.getAnnotationAlias(clazz), clazz);
}
/** * 通过clazz 来获取到里面的title数据 * @param clazz * @return */
public static LinkedHashMap<String,String> getAnnotationAlias(Class<?> clazz){
Field [] fields = clazz.getDeclaredFields();
LinkedHashMap<String,String> title = new LinkedHashMap<String, String>();
for(Field field:fields){
HFFSAlias alias = field.getAnnotation(HFFSAlias.class);
if(alias != null){
title.put(field.getName(), alias.value());
}
}
return title;
}
/** * 没有行标题的数据,需要通过这种方法来确定数据 * 这个方法使用起来 比通过Map 更加的简便 * @param file 逆向工程的xls文件爱你 * @param title 这个 需要设定 映射关系,而且顺序不能错 * @param clazz 映射的类对象 * @param hasHead 是否包含表头,有表头的数据就 需要从1 开始 * @return */
public static List<?> reverseHSSF(File file,String[] title,Class<?> clazz ,boolean hasHead){
try {
//创建work
HSSFWorkbook work = PoiUtils.getHSSFWorkbook(file);
//获取第一页
HSSFSheet sheet = work.getSheetAt(0);
//row是从0开始的,获取列和行的数目
HSSFRow row = sheet.getRow(0);
List list =null;
List<String> dateFields = null;
int rownum = sheet.getLastRowNum();
int cellnum = row.getLastCellNum();
// System.out.println(rownum);
String [] ids = title;
if(rownum >0 && cellnum >0){
list = new ArrayList();
dateFields = PoiUtils.getDataTimeFiled(clazz);
//遍历别的数据,、第一行数据 是需要的
int start = 0;
if(hasHead){
start = 1;
}
for(int i=start;i<rownum;i++){
row = sheet.getRow(i);
//实例化对象
Object obj = clazz.newInstance();
//设定属性
for(int j=0;j<cellnum;j++){
HSSFCell cell = row.getCell((short)j);
String result ="";
//当时数字类型的数据,不可以转化
if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){
result = cell.getNumericCellValue()+"";
}else{
result =cell.getStringCellValue();
}
//日期类型的数据不好注入,需要判断,和设定数据的类型
if(dateFields.contains(ids[j])){
BeanUtils.setProperty(obj, ids[j], new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(result));
}else{
BeanUtils.setProperty(obj, ids[j], result);
}
}
list.add(obj);
}
}
return list;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/** * 通过配置的方法来生成逆向工程 * 给定文件,将对象转化为类 * 设定中,sheeet 就一页 * @param file xls文件名称 * @param title 标题,表头关系 * @param clazz 类对象 * @return */
public static List<?> reverseHSSF(File file,Map<String,String> title,Class<?> clazz ){
try {
//创建work
HSSFWorkbook work = PoiUtils.getHSSFWorkbook(file);
//获取第一页
HSSFSheet sheet = work.getSheetAt(0);
//row是从0开始的,获取列和行的数目
HSSFRow row = sheet.getRow(0);
List list =null;
List<String> dateFields = null;
int rownum = sheet.getLastRowNum();
int cellnum = row.getLastCellNum();
// System.out.println(rownum);
String [] ids = new String[cellnum];
if(rownum >0 && cellnum >0){
title = PoiUtils.reverseMap(title);
list = new ArrayList();
dateFields = PoiUtils.getDataTimeFiled(clazz);
//这个可以 提取为 获取第一行数据的信息
//获取地一行,标题行 获取标题对应信息
for(int j=0;j<cellnum;j++){
HSSFCell cell = row.getCell((short)j);
String result = "";
//当时数字类型的数据,不可以转化
if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){
// ids[j]= title.containsValue(value);
result = cell.getNumericCellValue()+"";
}else{
result = cell.getStringCellValue();
}
//获取的数据进行注入操作
//需要反转map集合
if(title != null && result != null){
if(!title.containsKey(result)){
throw new RuntimeException("内容不匹配");
}
ids[j] = title.get(result);
}
}
//遍历别的数据
for(int i=1;i<rownum;i++){
row = sheet.getRow(i);
//实例化对象
Object obj = clazz.newInstance();
//设定属性
for(int j=0;j<cellnum;j++){
HSSFCell cell = row.getCell((short)j);
String result ="";
//当时数字类型的数据,不可以转化
if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){
result = cell.getNumericCellValue()+"";
}else{
result =cell.getStringCellValue();
}
//日期类型的数据不好注入,需要判断,和设定数据的类型
if(dateFields.contains(ids[j])){
BeanUtils.setProperty(obj, ids[j], new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(result));
}else{
BeanUtils.setProperty(obj, ids[j], result);
}
}
list.add(obj);
}
}
return list;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/** * 通过给定类,来获取到日期类型的字段 * 解决日期类型 注入不进去的问题 * @param clazz * @return */
public static List<String> getDataTimeFiled(Class<?> clazz){
Field [] fields = clazz.getDeclaredFields();
List<String> fieldNames = new ArrayList<String>();
for(Field field :fields){
if(field.getType().getSimpleName().equals("Date")){
fieldNames.add(field.getName());
}
}
return fieldNames;
}
/** * 创建我们的HSSFwork 通过配置别名属性 * @param tiles 文档的title Map<属性名称, 别名> * @param date List<?> 是一个集合的数据 * @return */
public static HSSFWorkbook createHSSF(LinkedHashMap<String, String> title,List<?> data) {
try {
//一个汉字的宽度是 500
//英文260
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet();
//获取字符的长度
// sheet.setColumnGroupCollapsed(columnNumber, collapsed);
// sheet.setColumnWidth(column, width);
//写title中的数据
if(title!= null && title.size() >0){
//创建样式
HSSFCellStyle style = PoiUtils.getHeadStyle(workbook);
int index = 0;
HSSFRow row = sheet.createRow(0);
//获取宽度
Map<String, Short> columns = PoiUtils.getColumnWidth(title, data);
//获取数据
for(Map.Entry<String, String> entry:title.entrySet()){
String val = entry.getValue();
HSSFCell cell = row.createCell((short)index);
//设置单元格的类型
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
//设置编码格式
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
//设定样式
cell.setCellStyle(style);
cell.setCellValue(val);
sheet.setColumnWidth((short)index, columns.get(entry.getKey()));
index++;
}
//创建样式
style = PoiUtils.getBodyStyle(workbook);
//设定数据
if(data != null && data.size() >0){
//获取我们的 data中的数据
//内容从第二行开始
int rownum =1;
for(Object obj:data){
row = sheet.createRow(rownum);
//列从第一行开始
int colnum = 0;
for(Map.Entry<String, String> entry:title.entrySet()){
String key = entry.getKey();
//获取装的对象数据
String val = BeanUtils.getProperty(obj, key);
if(val == null || "".equals(val.trim())){
val = "-";
}
HSSFCell cell = row.createCell((short)colnum);
//设置单元格的类型
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
//设置编码格式
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
//设定样式
cell.setCellStyle(style);
cell.setCellValue(val);
//增加列
colnum ++;
}
rownum ++;
}
}
}
return workbook;
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/** * 计算每个数据列的最适合的宽度 * 通过数据来获取到我们的数据的宽度 * @param title 数据标题 * @param data 数据几何 * @return */
public static Map<String, Short> getColumnWidth(Map<String, String> title,List<?> data){
try {
//存储我们的每个属性的宽度
//Map<属性明,宽度>
Map<String,Short> map = new HashMap<String, Short>();
if(title!= null && title.size() >0){
//计算我们的第一行的宽度
for(Map.Entry<String, String> entry:title.entrySet()){
int max = PoiUtils.countColumnLength(entry.getValue());
//计算下面的所有 同一字段的长度
for(Object obj:data){
String val = BeanUtils.getProperty(obj, entry.getKey());
int temp = PoiUtils.countColumnLength(val);
max = max >temp?max:temp;
}
//设定字段的值
map.put(entry.getKey(), (short)max);
}
}
return map;
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/** * * 根据字符的数量,来计算列的宽度 * 不同的数据他的数据长度是不一样的,所以我们需要计算总体的长度 * @param str 字符串 * @return int 计算出列宽 * */
private static int countColumnLength(String str){
int len =0;
if(str != null && !"".equals(str.trim())){
//一个汉字的宽度是 500
//英文260
char [] chars = str.toCharArray();
//z
int chines = 0;
int other =0;
for(char val:chars){
if(PoiUtils.isChineseChar(val)){
chines ++;
}else{
other++;
}
}
//获取到了数据然后计算长度
len = chines*500+other*260+1000;
}
return len;
}
/** * 创建样式 * @return */
public static HSSFCellStyle getHeadStyle(HSSFWorkbook workbook){
HSSFCellStyle style = workbook.createCellStyle();
//居中显示
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
style.setFont(PoiUtils.getSongRed10(workbook));
return style;
}
/** * 创建样式 * @return */
public static HSSFCellStyle getBodyStyle(HSSFWorkbook workbook){
HSSFCellStyle style = workbook.createCellStyle();
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
style.setFont(PoiUtils.getSong10(workbook));
return style;
}
/** * 创建字体 * @param fontFamly 字体 * @param size 大小 字号 14 13 * @param color HSSFColor.BLACK.index 这个HSSFColor中 的数据 * @param isBold 是否加粗 操作 * @return */
//字号 是14号 -->对应的short 280宽度 是 14好字体
public static HSSFFont creatFont(HSSFWorkbook workbook,String fontFamly,Integer size,Short color,boolean isBold){
HSSFFont font = workbook.createFont();
//字号 是14号 -->对应的short 宽度 是 14好字体
font.setFontHeight((short)((size== null || size ==0 )?280:size*20));
font.setBoldweight(isBold?HSSFFont.BOLDWEIGHT_BOLD:HSSFFont.BOLDWEIGHT_NORMAL);
//将字体颜色变红色
// short colors = HSSFColor.RED.index;
font.setColor(color== null?HSSFColor.BLACK.index:color);
return font;
}
/** * 判断我们的数据 中,是否有中文字符 * @param str * @return */
public static boolean isChineseChar(char str){
boolean flag = false;
Pattern pattern = Pattern.compile("[\u4e00-\u9fa5]");
if(pattern.matcher(str+"").find()){
flag = true;
}
return flag;
}
/** * 得到宋体10 号 黑色 * 默认字体的大小就是10 号 * @return */
public static HSSFFont getSong10(HSSFWorkbook workbook){
return creatFont(workbook,"宋体", 10, null,false);
}
/** * 得到宋体10 号 红色加粗 * @return */
public static HSSFFont getSongRed10(HSSFWorkbook workbook){
return creatFont(workbook,"宋体", 10, HSSFColor.RED.index,true);
}
/** * 将Map * @param map * @return */
public static Map<String,String> reverseMap(Map<String,String> map){
Map<String,String> newMap = null;
//将便利的结果重新装填一下
if(map != null && map.size() >0){
newMap = new LinkedHashMap<String, String>();
for(Map.Entry<String, String> entry:map.entrySet()){
newMap.put(entry.getValue(), entry.getKey());
}
}
return newMap;
}
}
测试类
主要测试了,自己手动编写表头和自动配置生成表头两种方法,同时还测试了Excel转List数据
package com.yellowcong.test;
import java.io.File;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.yellowcong.model.Pager;
import com.yellowcong.model.SystemContext;
import com.yellowcong.model.User;
import com.yellowcong.service.UserService;
import com.yellowcong.utils.HFFSAlias;
import com.yellowcong.utils.PoiUtils;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:ApplicationContext.xml")
public class UserServiceTest {
private UserService userService;
@Resource(name="userService")
public void setUserService(UserService userService) {
this.userService = userService;
}
@Test
public void testList(){
List<User> users= userService.listByPager().getData();
//可以做成annotation的
Map<String,String> map = new HashMap<String, String>();
map.put("id", "编号");
map.put("username", "用户名");
map.put("nickname", "别名");
map.put("password", "密码");
map.put("createDate", "创建日期");
map.put("imgUrl", "头像路径");
map.put("email", "邮箱");
map.put("phone", "电话");
map.put("isActive", "是否激活");
HSSFWorkbook work = PoiUtils.createHSSF(map, users);
PoiUtils.copyHSSFToFile(work, new File("D:/users.xls"));
}
@Test
public void testList2Anotation(){
List<User> users= userService.listByPager().getData();
HSSFWorkbook work = PoiUtils.createHSSFByAnnotation(users);
PoiUtils.copyHSSFToFile(work, new File("D:/users.xls"));
}
}