如何使用 JdbcTemplate 从 MySQL 转换类型 Point?
How convert type Point from MySQL with JdbcTemplate?
需要帮助!我无法使用 JdbcTemplate 从 MySQL 转换点。但是,当使用 JPA 时,它成功了!
不要建议 SELECT ST_AsWKT() + WKTReader()
的答案,我已经尝试过但效果不是很好...
也许您需要为 JDBC 指定 SpatialDialect 或编写自定义转换器???
错误:
java.sql.SQLException: Conversion not supported for type org.locationtech.jts.geom.Point
来源
输入Mysql:POINT NULL SRID 4326
输入Java:org.locationtech.jts.geom.Point
在Gradle中:
implementation 'org.locationtech.jts:jts-core:1.18.0'
NtpJdbcRepository.java
:
import org.locationtech.jts.geom.Point;
@Repository
@AllArgsConstructor
public class NtpJdbcRepository implements NtpRepository {
private final JdbcTemplate jdbcTemplate;
@Override
public List<NtpSource> getNtpSourceWithNtpIdandDate(List<Integer> ntp_id, Date startDate, Date endDate) {
String selectQuery = "SELECT "
+ "u.idTable,"
+ "u.ntp_id,"
+ "u.dataDT,"
+ "u.ip,"
+ "u.country,"
+ "u.iso_code,"
+ "u.city,"
+ "u.coordinates,"
+ "SUM(u.counts_requests) as counts_requests "
+ "FROM ntp_source AS u "
+ "WHERE u.ntp_id IN (?) and u.dataDT BETWEEN ? AND ? "
+ "GROUP BY u.ip";
String inSql = ntp_id.stream().map(String::valueOf)
.collect(Collectors.joining(","));
return jdbcTemplate.query(selectQuery,
new Object[]{inSql, startDate, endDate},
(rs, rowNum) -> {
NtpSource ntpSource = new NtpSource();
ntpSource.setId(rs.getInt("idTable"));
ntpSource.setNtpId(rs.getInt("ntp_id"));
ntpSource.setDataDT(rs.getDate("dataDT"));
ntpSource.setIp(rs.getString("ip"));
ntpSource.setCountry(rs.getString("country"));
ntpSource.setIsoCode(rs.getString("iso_code"));
ntpSource.setCity(rs.getString("city"));
ntpSource.setCountsRequests(rs.getLong("counts_requests"));
ntpSource.setCoordinates(rs.getObject("coordinates", Point.class)); // <-- There error: SQLException: Conversion not supported for type org.locationtech.jts.geom.Point
return ntpSource;
});
}
}
NtpSource.java
:
import org.locationtech.jts.geom.Point;
@Entity
@Table(name = "ntp_source")
@ToString
@Data
@NoArgsConstructor
@AllArgsConstructor
public class NtpSource implements Serializable {
@Id
@Column(name = "idTable")
private int id;
@Column(name = "ntp_id")
private int ntpId;
@Column(name = "dataDT", columnDefinition="DATETIME")
@Temporal(TemporalType.TIMESTAMP)
private Date dataDT;
private String ip;
private String country;
@Column(name = "iso_code")
private String isoCode;
private String city;
@Column(name = "coordinates", nullable = true, columnDefinition = "POINT SRID 4326")
public Point coordinates;
@Column(name = "counts_requests")
private long countsRequests;
}
使用 JPA 的模拟请求 (NtpJpaRepository.java
):
@Repository
public interface NtpJpaRepository extends JpaRepository<NtpSource, Long> {
@Query(nativeQuery = true, value = "SELECT "
+"u.idTable,"
+"u.ntp_id, "
+"u.dataDT, "
+"u.ip,"
+"u.country,"
+"u.iso_code,"
+"u.city,"
+"u.coordinates,"
+"SUM(u.counts_requests) as counts_requests"
+"FROM ntp_source AS u "
+"WHERE u.ntp_id IN (:ntp_id) and u.dataDT BETWEEN :startDate AND :endDate "
+"GROUP BY u.ip")
List<NtpSource> getNtpSourceWithNtpIdandDate(
@Param("ntp_id") List<Integer> ntp_id,
@Param("startDate") Date startDate,
@Param("endDate") Date endDate);
...
}
application.properties
:
spring.datasource.url=jdbc:mysql://localhost/ntp
spring.datasource.username=*****
spring.datasource.password=*****
spring.sql.init.encoding=UTF-8
spring.datasource.connectionProperties=useUnicode=true;
org.springframework.data.jdbc.repository.config.DialectResolver$JdbcDialectProvider=org.hibernate.spatial.dialect.mysql.MySQL8SpatialDialect #testing
server.servlet.context-path=/ntp_visits
server.port = 8090
## Hibernate & JPA Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.spatial.dialect.mysql.MySQL8SpatialDialect
spring.jpa.hibernate.ddl-auto = update
spring.jpa.show-sql=true
#spring.jpa.properties.hibernate.jdbc.batch_size=3500
spring.jpa.open-in-view=false
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.springframework.jdbc.core = TRACE
#logging.level.org.hibernate.SQL=DEBUG
#logging.level.org.hibernate.type=trace
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.properties.hibernate.use_sql_comments=false
# THYMELEAF (ThymeleafAutoConfiguration)
#spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.web.resources.add-mappings=true
spring.thymeleaf.cache=true
build.gradle
:
plugins {
id 'org.springframework.boot' version '2.5.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id 'war'
}
group = 'org.vniiftri'
version = '1.0'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
configurations.all {
exclude module: 'slf4j-log4j12'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.google.code.gson:gson:2.8.7'
implementation group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'
//implementation 'org.locationtech.jts:jts:1.18.0'
implementation 'org.locationtech.jts:jts-core:1.18.0'
//implementation 'org.n52.jackson:jackson-datatype-jts:1.2.4'
//implementation group: 'com.bedatadriven', name: 'jackson-datatype-jts', version: '2.4'
implementation 'org.hibernate:hibernate-core:5.5.5.Final'
implementation group: 'org.hibernate', name: 'hibernate-spatial', version: '5.5.5.Final'
//implementation group: 'com.graphhopper.external', name: 'jackson-datatype-jts', version: '0.10-2.5-1'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
假设您只想“提取”点几何体,
我认为您应该查看 Hibernate Spatial 如何从 JDBC ResultSet 中提取 JTS Geometry - 您的起点应该是 org.hibernate.spatial.dialect.oracle.SDOGeometryValueExtractor
(如果您还想“存储”几何图形,我现在没有任何提示给您)
代码(改编自上述 class)可能如下所示:
private Geometry loadGeometry(ResultSet rs) throws SQLException {
final Object geomObj = rs.getObject( "coordinates" );
if (rs.wasNull()) {
return null;
} else {
return convertToGeometry(geomObj);
}
}
private Geometry convertToGeometry(Object struct) {
final SDOGeometry sdogeom = SDOGeometry.load( (Struct) struct );
final var geolateGeom = toGeomerty( sdogeom );
return JTS.to(geolateGeom);
}
private org.geolatte.geom.Geometry toGeomerty(SDOGeometry sdoGeom) {
return Decoders.decode( sdoGeom );
}
我已经解决这个问题很久了,我会公布我是如何解决的。也许有更好的方法。
NtpJdbcRepository.java
:
@Repository
@AllArgsConstructor
public class NtpJdbcRepository implements NtpRepository {
private final NamedParameterJdbcTemplate namedJdbcTemplate;
private final PointReader pointReader;
@Override
public List<NtpSource> getNtpSource(List<Integer> ntp_id, Date startDate, Date endDate) {
String selectQuery = "SELECT "
+ "u.ntp_id, "
+ "u.ip,"
+ "u.country,"
+ "u.iso_code,"
+ "u.city,"
+ "ST_AsWKB(u.coordinates) as coordinates," // <- returns a binary result.
+ "SUM(u.counts_requests) as counts_requests "
+ "FROM ntp_source AS u "
+ "WHERE u.ntp_id IN (:ntp_ids) and u.dataDT BETWEEN :startDate AND :endDate "
+ "GROUP BY u.ip, u.ntp_id";
MapSqlParameterSource parameters = new MapSqlParameterSource()
.addValue("ntp_ids", ntp_id)
.addValue("startDate", startDate)
.addValue("endDate", endDate);
return namedJdbcTemplate.query(selectQuery,
parameters, (rs, rowNum) -> {
NtpSource ntpSource = new NtpSource();
ntpSource.setNtpId(rs.getInt("ntp_id"));
ntpSource.setIp(rs.getString("ip"));
ntpSource.setCountry(rs.getString("country"));
ntpSource.setIsoCode(rs.getString("iso_code"));
ntpSource.setCity(rs.getString("city"));
ntpSource.setCountsRequests(rs.getLong("counts_requests"));
ntpSource.setCoordinates(pointReader.read(rs.getBytes("coordinates"))); // <- get byte[]
return ntpSource;
});
}
}
PointReader.java
:
@Component
@Slf4j
public class PointReader {
private static final int SRID = 4326;
private final WKTReader reader;
private final WKBReader wkbReader;
private final GeometryFactory gf;
public PointReader() {
PrecisionModel pm = new PrecisionModel(PrecisionModel.FLOATING);
gf = new GeometryFactory(pm, SRID);
wkbReader = new WKBReader(gf);
reader = new WKTReader(gf);
}
public Point read(@NotNull byte[] bytes) {
if (bytes == null) {
return null;
} else {
Geometry geometry;
try {
geometry = wkbReader.read(bytes);
return convert3Dto2D(geometry);
} catch (ParseException e) {
log.error("Ошибка при чтении Point в виде байтов.", e);
return null;
}
}
}
// need the point to have only 2 measurements, but not three
private Point convert3Dto2D(Geometry g3D) {
Coordinate geomCoord = g3D.getCoordinate().copy();
CoordinateSequence seq = new PackedCoordinateSequenceFactory().create(1, 2);
seq.setOrdinate(0, CoordinateSequence.X, geomCoord.x);
seq.setOrdinate(0, CoordinateSequence.Y, geomCoord.y);
return gf.createPoint(seq);
}
}
需要帮助!我无法使用 JdbcTemplate 从 MySQL 转换点。但是,当使用 JPA 时,它成功了!
不要建议 SELECT ST_AsWKT() + WKTReader()
的答案,我已经尝试过但效果不是很好...
也许您需要为 JDBC 指定 SpatialDialect 或编写自定义转换器???
错误:
java.sql.SQLException: Conversion not supported for type org.locationtech.jts.geom.Point
来源
输入Mysql:POINT NULL SRID 4326
输入Java:org.locationtech.jts.geom.Point
在Gradle中:
implementation 'org.locationtech.jts:jts-core:1.18.0'
NtpJdbcRepository.java
:
import org.locationtech.jts.geom.Point;
@Repository
@AllArgsConstructor
public class NtpJdbcRepository implements NtpRepository {
private final JdbcTemplate jdbcTemplate;
@Override
public List<NtpSource> getNtpSourceWithNtpIdandDate(List<Integer> ntp_id, Date startDate, Date endDate) {
String selectQuery = "SELECT "
+ "u.idTable,"
+ "u.ntp_id,"
+ "u.dataDT,"
+ "u.ip,"
+ "u.country,"
+ "u.iso_code,"
+ "u.city,"
+ "u.coordinates,"
+ "SUM(u.counts_requests) as counts_requests "
+ "FROM ntp_source AS u "
+ "WHERE u.ntp_id IN (?) and u.dataDT BETWEEN ? AND ? "
+ "GROUP BY u.ip";
String inSql = ntp_id.stream().map(String::valueOf)
.collect(Collectors.joining(","));
return jdbcTemplate.query(selectQuery,
new Object[]{inSql, startDate, endDate},
(rs, rowNum) -> {
NtpSource ntpSource = new NtpSource();
ntpSource.setId(rs.getInt("idTable"));
ntpSource.setNtpId(rs.getInt("ntp_id"));
ntpSource.setDataDT(rs.getDate("dataDT"));
ntpSource.setIp(rs.getString("ip"));
ntpSource.setCountry(rs.getString("country"));
ntpSource.setIsoCode(rs.getString("iso_code"));
ntpSource.setCity(rs.getString("city"));
ntpSource.setCountsRequests(rs.getLong("counts_requests"));
ntpSource.setCoordinates(rs.getObject("coordinates", Point.class)); // <-- There error: SQLException: Conversion not supported for type org.locationtech.jts.geom.Point
return ntpSource;
});
}
}
NtpSource.java
:
import org.locationtech.jts.geom.Point;
@Entity
@Table(name = "ntp_source")
@ToString
@Data
@NoArgsConstructor
@AllArgsConstructor
public class NtpSource implements Serializable {
@Id
@Column(name = "idTable")
private int id;
@Column(name = "ntp_id")
private int ntpId;
@Column(name = "dataDT", columnDefinition="DATETIME")
@Temporal(TemporalType.TIMESTAMP)
private Date dataDT;
private String ip;
private String country;
@Column(name = "iso_code")
private String isoCode;
private String city;
@Column(name = "coordinates", nullable = true, columnDefinition = "POINT SRID 4326")
public Point coordinates;
@Column(name = "counts_requests")
private long countsRequests;
}
使用 JPA 的模拟请求 (NtpJpaRepository.java
):
@Repository
public interface NtpJpaRepository extends JpaRepository<NtpSource, Long> {
@Query(nativeQuery = true, value = "SELECT "
+"u.idTable,"
+"u.ntp_id, "
+"u.dataDT, "
+"u.ip,"
+"u.country,"
+"u.iso_code,"
+"u.city,"
+"u.coordinates,"
+"SUM(u.counts_requests) as counts_requests"
+"FROM ntp_source AS u "
+"WHERE u.ntp_id IN (:ntp_id) and u.dataDT BETWEEN :startDate AND :endDate "
+"GROUP BY u.ip")
List<NtpSource> getNtpSourceWithNtpIdandDate(
@Param("ntp_id") List<Integer> ntp_id,
@Param("startDate") Date startDate,
@Param("endDate") Date endDate);
...
}
application.properties
:
spring.datasource.url=jdbc:mysql://localhost/ntp
spring.datasource.username=*****
spring.datasource.password=*****
spring.sql.init.encoding=UTF-8
spring.datasource.connectionProperties=useUnicode=true;
org.springframework.data.jdbc.repository.config.DialectResolver$JdbcDialectProvider=org.hibernate.spatial.dialect.mysql.MySQL8SpatialDialect #testing
server.servlet.context-path=/ntp_visits
server.port = 8090
## Hibernate & JPA Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.spatial.dialect.mysql.MySQL8SpatialDialect
spring.jpa.hibernate.ddl-auto = update
spring.jpa.show-sql=true
#spring.jpa.properties.hibernate.jdbc.batch_size=3500
spring.jpa.open-in-view=false
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.springframework.jdbc.core = TRACE
#logging.level.org.hibernate.SQL=DEBUG
#logging.level.org.hibernate.type=trace
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.properties.hibernate.use_sql_comments=false
# THYMELEAF (ThymeleafAutoConfiguration)
#spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.web.resources.add-mappings=true
spring.thymeleaf.cache=true
build.gradle
:
plugins {
id 'org.springframework.boot' version '2.5.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id 'war'
}
group = 'org.vniiftri'
version = '1.0'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
configurations.all {
exclude module: 'slf4j-log4j12'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.google.code.gson:gson:2.8.7'
implementation group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'
//implementation 'org.locationtech.jts:jts:1.18.0'
implementation 'org.locationtech.jts:jts-core:1.18.0'
//implementation 'org.n52.jackson:jackson-datatype-jts:1.2.4'
//implementation group: 'com.bedatadriven', name: 'jackson-datatype-jts', version: '2.4'
implementation 'org.hibernate:hibernate-core:5.5.5.Final'
implementation group: 'org.hibernate', name: 'hibernate-spatial', version: '5.5.5.Final'
//implementation group: 'com.graphhopper.external', name: 'jackson-datatype-jts', version: '0.10-2.5-1'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
假设您只想“提取”点几何体,
我认为您应该查看 Hibernate Spatial 如何从 JDBC ResultSet 中提取 JTS Geometry - 您的起点应该是 org.hibernate.spatial.dialect.oracle.SDOGeometryValueExtractor
(如果您还想“存储”几何图形,我现在没有任何提示给您)
代码(改编自上述 class)可能如下所示:
private Geometry loadGeometry(ResultSet rs) throws SQLException {
final Object geomObj = rs.getObject( "coordinates" );
if (rs.wasNull()) {
return null;
} else {
return convertToGeometry(geomObj);
}
}
private Geometry convertToGeometry(Object struct) {
final SDOGeometry sdogeom = SDOGeometry.load( (Struct) struct );
final var geolateGeom = toGeomerty( sdogeom );
return JTS.to(geolateGeom);
}
private org.geolatte.geom.Geometry toGeomerty(SDOGeometry sdoGeom) {
return Decoders.decode( sdoGeom );
}
我已经解决这个问题很久了,我会公布我是如何解决的。也许有更好的方法。
NtpJdbcRepository.java
:
@Repository
@AllArgsConstructor
public class NtpJdbcRepository implements NtpRepository {
private final NamedParameterJdbcTemplate namedJdbcTemplate;
private final PointReader pointReader;
@Override
public List<NtpSource> getNtpSource(List<Integer> ntp_id, Date startDate, Date endDate) {
String selectQuery = "SELECT "
+ "u.ntp_id, "
+ "u.ip,"
+ "u.country,"
+ "u.iso_code,"
+ "u.city,"
+ "ST_AsWKB(u.coordinates) as coordinates," // <- returns a binary result.
+ "SUM(u.counts_requests) as counts_requests "
+ "FROM ntp_source AS u "
+ "WHERE u.ntp_id IN (:ntp_ids) and u.dataDT BETWEEN :startDate AND :endDate "
+ "GROUP BY u.ip, u.ntp_id";
MapSqlParameterSource parameters = new MapSqlParameterSource()
.addValue("ntp_ids", ntp_id)
.addValue("startDate", startDate)
.addValue("endDate", endDate);
return namedJdbcTemplate.query(selectQuery,
parameters, (rs, rowNum) -> {
NtpSource ntpSource = new NtpSource();
ntpSource.setNtpId(rs.getInt("ntp_id"));
ntpSource.setIp(rs.getString("ip"));
ntpSource.setCountry(rs.getString("country"));
ntpSource.setIsoCode(rs.getString("iso_code"));
ntpSource.setCity(rs.getString("city"));
ntpSource.setCountsRequests(rs.getLong("counts_requests"));
ntpSource.setCoordinates(pointReader.read(rs.getBytes("coordinates"))); // <- get byte[]
return ntpSource;
});
}
}
PointReader.java
:
@Component
@Slf4j
public class PointReader {
private static final int SRID = 4326;
private final WKTReader reader;
private final WKBReader wkbReader;
private final GeometryFactory gf;
public PointReader() {
PrecisionModel pm = new PrecisionModel(PrecisionModel.FLOATING);
gf = new GeometryFactory(pm, SRID);
wkbReader = new WKBReader(gf);
reader = new WKTReader(gf);
}
public Point read(@NotNull byte[] bytes) {
if (bytes == null) {
return null;
} else {
Geometry geometry;
try {
geometry = wkbReader.read(bytes);
return convert3Dto2D(geometry);
} catch (ParseException e) {
log.error("Ошибка при чтении Point в виде байтов.", e);
return null;
}
}
}
// need the point to have only 2 measurements, but not three
private Point convert3Dto2D(Geometry g3D) {
Coordinate geomCoord = g3D.getCoordinate().copy();
CoordinateSequence seq = new PackedCoordinateSequenceFactory().create(1, 2);
seq.setOrdinate(0, CoordinateSequence.X, geomCoord.x);
seq.setOrdinate(0, CoordinateSequence.Y, geomCoord.y);
return gf.createPoint(seq);
}
}