在Java中使用ModelMapper简化Shapefile属性转JavaBean实战

目录

前言

一、原始的处理办法

1、使用Set方法来转换

2、使用构造方法转换

二、基于ModelMapper的动态转换

1、ModelMapper简介

2、集成到项目中

3、Shapefile属性读取

三、总结


前言

在现代软件开发中,尤其是在多层架构中,经常需要将数据从一个域对象转换到另一个域对象,或者从数据库结果集转换到业务对象。手动编写这些转换代码不仅耗时,而且容易出错。我们需要一种自动化的方式来处理这些转换,减少了开发工作量和潜在的错误。JavaBean是一种遵循特定编写规范的Java类,通常具有get和set方法来访问私有属性,以及一个无参构造函数。JavaBean是我们平时的开发过程当中遇到最多的类,不管它是DO、DTO、VO或者POJO等等,这些类都是以JavaBean的形式存在的。

在之前的系列博客中,尤其是涉及空间数据管理和查询的后台设计中。大家如果认真注意的话,我们分别在空间数据库中创建一张与属性字段对应的空间表。而在采用MVC的开发模式中,模型层通常会对应一个与空间表对应的空间表。与成熟的ORM映射不同的是,ORM框架自动的会将数据库中的字段快速得与JavaBean的属性进行对应,在执行查询语句的时候可以直接关联。但是在处理空间数据时,比如使用GDAL或者GeoTools来进行数据读取时,目前暂时没有找到成熟的框架或者组件支持空间属性表与JavaBean的快速映射需求。

本文以Java语言为例,主要讲解如何使用Java语言进行空间数据的读取,空间属性信息的读取使用GeoTools,文章首先介绍最原始的做法,即在对象中自定义转换方法来实现转换,然后详细介绍一种基于ModelMapper的空间属性映射实现方法。ModelMapper在Java的其它领域应用很多,但是在GIS领域中使用的还不多。如果您是一名GISER,同时也面临着属性信息映射的问题,不妨来这里交流讨论。

一、原始的处理办法

在介绍本文的处理办法之前,首先依然来看一下最原始的处理办法是什么。由此,可以对比不同的处理办法的不同点,也可以发现其有点。关于如何读取空间属性信息,不管是使用GDAL或者GeoTools,不管是Gdb数据或者Shapefile数据,之前的系列博客都进行了简单的说明。因此想了解具体的读取过程的,可以翻阅之前的博客。因此这里只将转换的过程进行说明。

1、使用Set方法来转换

首先来介绍调用对象实例的Set方法来进行转换。这种情况使用与空间属性表的字段不是很多,我们在转换时可以先从空间属性表中分别读取出具体的字段,然后再调用对应的JavaBea的Set方法来进行字段的映射和转换。比如在进行省份的空间属性信息转换的代码如下所示:

java 复制代码
List<Province> list = new ArrayList<Province>();
for (int i = 0; i < featureCount; i++) {
	Feature feature = layer.GetFeature(i);
	String code = feature.GetFieldAsString("province_c");
	String name = feature.GetFieldAsString("province_n");
	String type = feature.GetFieldAsString("type");
	Geometry geom = feature.GetGeometryRef();
	String wkt = geom.ExportToWkt();
	Province p = new Province();
	p.setCode(code);
	p.setName(name);
	p.setType(type);
	p.setGeom(wkt);
}

上面是一个使用GDAL解析空间数据时的属性映射的实例代码。 这种方式相信大家很熟悉,通过创建Province对象后,再分别从feature中读取省份信息,最后设置到实例对象中,最后再保存到数据库中。

2、使用构造方法转换

除了使用set方法来进行设置,我们也可以使用构造方法来进行属性赋值。与set方法相比,构造方法可以统一设置,不需逐行进行设置。这样代码显得比较优雅,可读性也高。构造方法是将属性赋值的过程抽象都对象的构造方法中,这样实现代码的整体复用。这里以城市对象构造方法为例:

java 复制代码
public City(String provinceCode, String provinceName, String cityCode, String cityName, String type, String geom) {
	super();
	this.provinceCode = provinceCode;
	this.provinceName = provinceName;
	this.cityCode = cityCode;
	this.cityName = cityName;
	this.type = type;
	this.geom = geom;
}

然后在解析的过程中就可以直接调用构造方法的模式简化设置的过程,代码如下:

java 复制代码
List<City> list = new ArrayList<City>();
for (int i = 0; i < featureCount; i++) {
	Feature feature = layer.GetFeature(i);
	String code = feature.GetFieldAsString("province_c");
	String name = feature.GetFieldAsString("province_n");
	String cityCode = feature.GetFieldAsString("city_code");
	String cityName = feature.GetFieldAsString("city_name");
	String type = feature.GetFieldAsString("type");
	Geometry geom = feature.GetGeometryRef();
    String wkt = geom.ExportToWkt();
	list.add(new City(code,name,cityCode,cityName,type,wkt));
}
cityService.saveBatch(list,100);

可以从代码中看到,通过构造方法的方式来进行赋值,能极大的减少set方法的调用,能减少许多的代码调用。可以看到,不管使用哪种方式,我们都需要有一个从feature中根据属性名解析字段,不得不说,这种方式于我们最开始学JDBC的模式非常相似,那么有什么办法实现动态映射,不需要单独读取呢?下面我们就来分享一种方法来进行转换。

二、基于ModelMapper的动态转换

既然有了如上的需求场景,那么有什么方法可以实现快速的属性映射吗?在Java当中,很多人首选肯定是采用反射,对吧。确实如此,使用反射可以解决我们的问题,实现上述的需求。对于反射,相信很多的朋友可以自行进行编码。本着不重复造轮子的思路,我们可以从现有的一些成熟工具中来选择符合我们期望的组件。这里推荐一款转换组件,ModelMapper。

1、ModelMapper简介

ModelMapper是一个Java对象映射库,它能够将一个对象的数据映射到另一个对象中,从而避免手动编写数据转换代码。ModelMapper通过使用简单的配置和API,能够自动地将源对象的属性复制到目标对象的属性中,这在处理不同数据层之间的数据转换时非常有用。ModelMapper简化了开发流程,因为它减少了手动编写数据转换代码的需要。这不仅提高了开发效率,还使得代码更加简洁和易于维护。通过使用ModelMapper,开发者可以将更多的精力投入到业务逻辑的实现上,而不是数据转换的细节上。ModelMapper提供了高度的灵活性和可配置性。开发者可以根据需要自定义映射规则,例如跳过某些属性的映射,或者在映射过程中进行条件判断和自定义转换。这种灵活性使得ModelMapper能够适应各种复杂的数据转换场景。

首先,我们来看一下ModelMapper的官网介绍,modelmapper官网。大家可以先到官网看一下它的相关介绍可帮助文档。

2、集成到项目中

对ModelMapper有了基本的了解之后,我们来看一下如何将ModelMapper集成到Java项目当中。首先我们需要使用Maven来进行资源的引入,在Pom.xml中引入依赖,关键代码如下所示:

XML 复制代码
<!--  增加模型映射 add by 夜郎king in 2024.11.11 begin -->
<!-- https://mvnrepository.com/artifact/org.modelmapper/modelmapper -->
<dependency>
	<groupId>org.modelmapper</groupId>
	<artifactId>modelmapper</artifactId>
	<version>3.1.1</version>
</dependency>
<!--  modelmapper add by 夜郎king in 2024.11.11 end -->

然后在Java中进行相应的集成,虽然ModelMapper本身主要作用是用于JavaBean之间的转换,但是也可以在Map和JavaBean之间进行属性映射。

java 复制代码
@Test
public void convertMap2BeanWithUnderscoreNamingConvention() {
	HashMap<String, Object> map = new HashMap<String, Object>();
	map.put("scalerank", 6);
	map.put("LS_NAME", "test241111");
	map.put("MAX_POP10", "23562");
	ModelMapper modelMapper = new ModelMapper();
	Ne10mPopulatedPlaces pp = modelMapper.map(map, Ne10mPopulatedPlaces.class);
	System.out.println(pp);
	System.out.println(pp.getLsName());
	System.out.println(pp.getMaxPop10());
	assertEquals("test241111", pp.getLsName());
	assertEquals(23562L, pp.getMaxPop10());
}

代码很简单,首先定义一个HashMap,map的key是属性的名字,value是实际的值。然后我们创建ModelMapper,使用默认的转换策略和匹配模式,在这种策略下实现Map向Ne10mPopulatedPlaces对象的转换。Ne10mPopulatedPlaces对象就是之前提到过的人口城市空间属性数据。

这个类的属性比较对,大概有137个属性。针对这么多的属性映射,不管是采用set方法还是构造方法,实现代码都会非常冗长。运行上面的测试方法后,可以看到如下结果:

3、Shapefile属性读取

有了以上的基础,结合之前的GeoTools的属性读取方法,我们来解析属性表格。解析的思路很简单,循环属性表格,将属性表列和值组成一个HashMap,表头为key,值为value的hashMap,然后将这个HashMap转换成对应的JavaBean对象。

java 复制代码
@Test
public void convertDbf2BeanByDefault() throws Exception {
	File dbfFile = new File(SHP_FILE);
	ShpFiles shpFile = new ShpFiles(dbfFile);
	DbaseFileReader dbfReader = new DbaseFileReader(shpFile, true, Charset.defaultCharset());
	// 读取 DBF 文件的头信息
	DbaseFileHeader header = dbfReader.getHeader();
	List<Ne10mPopulatedPlaces> dataList = new ArrayList<Ne10mPopulatedPlaces>(header.getNumRecords());
		
	List<HashMap<String, Object>> mapList = new ArrayList<HashMap<String,Object>>();
		
	ModelMapper modelMapper = new ModelMapper();
	
	while (dbfReader.hasNext()) {
		Row row = dbfReader.readRow();
		HashMap<String, Object> map = new HashMap<String, Object>();
		for (int i = 0; i < header.getNumFields(); i++) {
			map.put(header.getFieldName(i), row.read(i));
		}
		mapList.add(map);
	}
		
	int index = 0;
	for(HashMap<String, Object> map : mapList) {
		if(index > 10) {
			break;
		}
		Ne10mPopulatedPlaces places = modelMapper.map(map, Ne10mPopulatedPlaces.class);
		System.out.println(places);
		dataList.add(places);
		index ++;
	}
	System.out.println(dataList.size());
	System.out.println("属性字段数:" + header.getNumFields());
	System.out.println("数据记录数:" + header.getNumRecords());
	dbfReader.close();
}

也是默认的映射策略和模式,通过上述的代码可以看到以下输出:

可以看到, 通过以上的代码已经成功的实现把HashMap转换成JavaBean,同时可以看到JavaBean的属性值都成功的进行了赋值。到此,大功告成。

三、总结

以上就是本文的主要内容,本文以Java语言为例,主要讲解如何使用Java语言进行空间数据的读取,空间属性信息的读取使用GeoTools,文章首先介绍最原始的做法,即在对象中自定义转换方法来实现转换,然后详细介绍一种基于ModelMapper的空间属性映射实现方法。ModelMapper在Java的其它领域应用很多,但是在GIS领域中使用的还不多。本文基于ModelMapper解决了在Shapefile文件读取过程中,如何实现动态的将属性表格映射到指定对象的方法进行了详细介绍。如果您是一名GISER,同时也面临着属性信息映射的问题,不妨来这里交流讨论。行文仓促,难免有许多不足之处,如有不足,还请各位专家朋友在评论区留言批评指正,不甚感激。