需求
有多张数据需要导入、导出,数据量巨大,决定采用easyExcel实现。
思路
easyExcel读web中的excel,由于不同的excel导入存在mysql中不同的表,若读取每类excel都新建一个ExcetListener处理save逻辑比较麻烦。考虑写一个通用的Listener,实现过程中发现由于excel中没有包含所有需要保存的字段(一部分字段由前端传到后台保存),故改为使用listener将数据读到内存中再进行批量保存处理。(隐患:数据量巨大时是否会导致内存问题)。
ObjectExcelListener objectExcelListener = new ObjectExcelListener();
EasyExcel.read(file.getInputStream(), SimpleConnector.class, objectExcelListener).
sheet().doRead();
List connectors = objectExcelListener.getDatas();
connectors.stream().forEach(connector ->{
connector.setProjectId(projectId);
connector.setArea(area);
connector.setLocation(location);
});
connectorDao.insertBatch(connectors);
使用listener将数据读到内存中
public class ObjectExcelListener extends AnalysisEventListener {
List addList = new ArrayList<>();
/**
* 每解析一行都会回调invoke()方法
* @param context 内容
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
//解析结束销毁不用的资源
//注意不要调用datas.clear(),否则getDatas为null
}
@Override
public void invoke(T t, AnalysisContext analysisContext) {
addList.add(t);
}
/**
* 返回数据
*
* @return 返回读取的数据集合
**/
public List getDatas() {
return addList;
}
/**
* 设置读取的数据集合
*
* @param datas 设置读取的数据集合
**/
public void setDatas(List datas) {
this.addList = datas;
}
}
实现过程中走的弯路
1.查找网上的资料,通用的Listener使用BaseDao构造,在Listener中调用dao去批量save数据。
附上通用listener实现
public class ExcetListener extends AnalysisEventListener {
private static final Logger LOGGER = LoggerFactory.getLogger(ExcetListener.class);
/**
* 每隔30条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 30;
List addList = new ArrayList<>();
private BaseDao baseDao;
ExcetListener(BaseDao baseDao) {
this.baseDao = baseDao;
}
/**
* 这个每一条数据解析都会来调用
*
* @param testCategory
* @param analysisContext
*/
@SneakyThrows
@Override
public void invoke(T testCategory, AnalysisContext analysisContext) {
addList.add(testCategory);
if (addList.size() >= BATCH_COUNT ) {
saveData();
addList.clear();
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param analysisContext
*/
@SneakyThrows
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
saveData();
}
/**
* 加上存储数据库
*/
private void saveData() {
if (addList.isEmpty()) {
return;
}
baseDao.insertBatch(addList);
}
}
BaseDao代码,所有的Dao都可以继承BaseDao
@Qualifier("qualityDataSource")
public interface BaseDao {
/**
* 根据实体对象新增记录.
*
* @param entity
* .
* @return id 返回插入数据的ID
*/
long insert(T entity);
int insertBatch(@Param("entities")List entities);
/**
* 更新实体对应的记录.
*
* @param entity
* .
* @return
*/
long update(T entity);
/**
* 根据ID查找记录.
*
* @param id
* .
* @return entity .
*/
T queryById(long id);
/**
* 根据ID删除记录.
*
* @param id
* .
* @return
*/
int deleteById(long id);
由于项目myBatis的配置,需要在对应的Mapper.xml中实现sql语句,子类的Dao接口仍然可以按原来的规则在接口中直接写BaseDao中没有的方法,例如
public interface SimpleConnectorDao extends BaseDao{
@Select("select distinct position from simple_connector where project_id=#{projectId} and area= #{area} and location=#{location}")
List getConnectorPosition(@Param("projectId") int projectId, @Param("area")String area, @Param("location")String location);
}
2.当前BaseDao为接口,没有实现类。实现类可自动生成Mapper.xml代码,不需要去自己写Mapper.xml的内容,参考github中https://github.com/xshiyu/mybatis-geneator-tool项目,注意点是由于项目采用多数据源配置,需要在注入时使用@Qualifier注明采用的数据源,下一步可实现在BaseDaoImpl中实现通用的inser,update等方法,即子类Dao不需要自己去实现增删改查。
当前项目BaseDaoImpl中的SqlSessionTemplate 注入方式
@Resource
@Qualifier("qualitySqlSessionTemplate")
private SqlSessionTemplate sessionTemplate;
@Autowired
@Qualifier("qualitySqlSessionFactory")
protected SqlSessionFactory sqlSessionFactory;
@Override
public SqlSession getSqlSession() {
return super.getSqlSession();
}
@Autowired
@Qualifier("qualitySqlSessionFactory")
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory){
super.setSqlSessionFactory(sqlSessionFactory);
}