토이 프로젝트를 진행 중, 장소의 위치의 위도, 경도 좌표를 저장해야 했는데, float type의 ‘latitude’, ‘longitude’라는 2개의 컬럼을 만들어서 각각 따로 저장하려고 했는데, 우연찮은 기회에 공간 데이터 타입이라는 것이 따로 있다는 것을 발견하여 적용해본 경험을 글로 작성하여 보려고 한다.
본론
공간(空間, 영어: space)은 어떤 물질 또는 물체가 존재할 수 있거나 어떤 일이 일어날 수 있는 장소이다.
공간 데이터란, 위 같은 점, 선, 면의 공간을 데이터화 한 것을 의미하는데, 필자는 지도에서 위치를 표시하기 위해 2D 공간 데이터인 점(Point) 데이터를 저장할 필요가 있었다. 필자는 Spring boot + JPA(Hibernate) + H2 환경을 사용하고 있어서, 공간 데이터를 다루기 위해 hibernate-spatial 의존성을 추가해주었다.
Caused by: org.h2.jdbc.JdbcSQLDataException: Value too longfor column "LOCATION BINARY VARYING(255)": "X'aced00057372001f6f72672e6c6f636174696f6e746563682e6a74732e67656f6d2e506f696e74... (1151)"; SQL statement: insert into place(id, create_date, update_date, jibun, location, road, zip, category_id, name)values(default, ?, ?, ?, ?, ?, ?, ?, ?) [22001-214] at org.h2.message.DbException.getJdbcSQLException(DbException.java:506) at org.h2.message.DbException.getJdbcSQLException(DbException.java:477) at org.h2.message.DbException.get(DbException.java:223) at org.h2.message.DbException.getValueTooLongException(DbException.java:322) at org.h2.value.Value.getValueTooLongException(Value.java:2573) at org.h2.value.Value.convertToVarbinary(Value.java:1371) at org.h2.value.Value.convertTo(Value.java:1125) at org.h2.value.Value.convertForAssignTo(Value.java:1092) at org.h2.table.Column.validateConvertUpdateSequence(Column.java:369) at org.h2.table.Table.convertInsertRow(Table.java:926) at org.h2.command.dml.Insert.insertRows(Insert.java:167) at org.h2.command.dml.Insert.update(Insert.java:135) at org.h2.command.CommandContainer.executeUpdateWithGeneratedKeys(CommandContainer.java:242) at org.h2.command.CommandContainer.update(CommandContainer.java:163) at org.h2.command.Command.executeUpdate(Command.java:252) at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:209) at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:169) at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197) ... 136 more
이처럼 예상하지 못한 결과가 발생하였는데… 로그를 보니, table create 시 좌표를 저장하는 컬럼의 타입이 기대했던 GEOMETRY이 아닌, varbinary 타입으로 생성되어서 발생한 에러인가 싶어서 컬럼 타입을 “GEOMETRY”로 지정하고 생성해봤지만, 결과는 마찬가지였다.
수정한 코드 및 실행된 SQL
1 2 3 4 5 6 7 8 9
...
// columnDefinition 속성으로 컬럼 타입 지정 @Column(name = "location", nullable = false, columnDefinition = "GEOMETRY") private Point location;
...
1 2 3 4 5 6 7 8 9 10 11 12
createtable place ( idbigintgeneratedbydefaultasidentity, create_date timestamp, update_date timestamp, jibun varchar(255), location GEOMETRY notnull, -- 정상적으로 컬럼의 데이터 타입이 GEOMETRY로 변경 road varchar(255), zip varchar(255) notnull, category_id bigint, namevarchar(255) notnull, primary key (id) )
다시 원인을 검색해본 결과… hibernate-spatial에서는 H2가 아닌 H2의 확장 데이터베이스인 GeoDB를 지원한다고 되어있는 것을 확인할 수 있었다.
The GeoDBDialect supports the GeoDB a spatial extension of the H2 in-memory database.
그 후 GeoDB를 적용하기 위해 properties 혹은 yaml에 아래처럼 dialect 설정에 org.hibernate.spatial.dialect.h2geodb.GeoDBDialect를 추가해 주면,
jpa: database-platform:org.hibernate.dialect.H2Dialect defer-datasource-initialization:true hibernate: ddl-auto:create-drop show-sql:true properties: hibernate: dialect:org.hibernate.spatial.dialect.h2geodb.GeoDBDialect# 추가 혹은 변경
dialect 설정 전 dialect
1
2022-11-3023:37:47.975 INFO 15720 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
dialect 설정 후 dialect
1
2022-11-3023:35:42.124 INFO 18400 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.spatial.dialect.h2geodb.GeoDBDialect