テーブルを作成する †
- 地理空間情報付きのつぶやきを WebSocket? で配信する
- つぶやきは Postgres に格納して、前回配信との差分を配信する
- Postgis に、つぶやきを格納するテーブルを作る
CREATE TABLE murmur_table (
id bigserial,
wtime timestamp without time zone,
wuser varchar(255),
message varchar(1023),
zoom integer,
geom geometry(Point,4326),
CONSTRAINT murmur_table_pkey PRIMARY KEY (id)
)
CREATE INDEX murmur_table_geom_idx
ON murmur_table
USING gist(geom);
- お試し
demo=# INSERT INTO murmur_table (wtime, wuser, message, zoom ,geom) VALUES
(current_timestamp, 'aho', 'hello', 12, ST_GeomFromText('POINT(135 35)', 4326));
demo=# SELECT id, wtime, wuser, message, zoom, ST_AsText(geom) AS location FROM murmur_table;
id | wtime | wuser | message | zoom | location
----+----------------------------+-------+---------+------+---------------
1 | 2014-01-05 22:09:27.393721 | aho | hello | 12 | POINT(135 35)
- 多分こんなかんじで配信するメッセージを取得する
SELECT id, wtime, wuser, message, zoom, ST_AsText(geom) AS location FROM murmur_table
where
zoom <= 13
and
wtime > timestamp '2014-01-05 20:00'
and
ST_Distance(
geom,
ST_GeomFromText('POLYGON((130 30, 130 40, 140 40, 140 30, 130 30))', 4326)
) = 0
order by id desc
offset 0 limit 50;
- 表示中の地図の zoom レベル 13
- 表示中の地図の矩形 (130 30)-(130 40)-(140 40)-(140 30)
- 前回配信 2014-01-05 20:00
- 日付変更線(東経180°=西経180°) マタギとかはあとで考えよう
Glassfish 4 に Postgis の接続プールを設定する †
- JDBCドライバの配置
- ant の設定
- postgis.jar
- postgresql.jar
- JDBC ドライバの置き場所
- ここで Glassfish を再起動
- Datasource の設定
- 管理コンソールで、接続プールを作り、接続プールに JNDI 名を割り付ける
- 接続プールを作る
プール名 | (任意) |
リソースタイプ | javax.sql.ConnectionPoolDataSource? |
ベンダー | Postgresql |
データソースクラス名 | org.postgresql.ds.PGConnectionPoolDataSource? |
詳細設定 | serverName | localhost |
databaseName | demo |
user | postgres |
password | postgres |
cf. Postgresql JDBC Driver Chapter 11. Connection Pools and Data Sources, http://jdbc.postgresql.org/documentation/92/ds-cpds.html
接続確認
- [JDBCリソース] で、接続プールに JNDI 名を割り付ける
つぶやきにアクセスする Singleton Session Bean を作る †
- Session Bean は、Web Profile (EJB lite) でも動くので、Maven Web プロジェクトを作る
- [Glassfish JPA] でやったように、DBMS から自動生成した Entity をひな形にアプリを作成
Entity (com.snail.gmurmur.MurmurTable?) †
package com.snail.gmurmur;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
import lombok.Data;
import org.postgis.PGgeometry;
@Data
@Entity
@Table(name = "murmur_table")
@XmlRootElement
@NamedQueries({
@NamedQuery(name = "MurmurTable.findAll", query = "SELECT m FROM MurmurTable m"),
@NamedQuery(name = "MurmurTable.findById", query = "SELECT m FROM MurmurTable m WHERE m.id = :id"),
@NamedQuery(name = "MurmurTable.findByWtime", query = "SELECT m FROM MurmurTable m WHERE m.wtime = :wtime"),
@NamedQuery(name = "MurmurTable.findByWuser", query = "SELECT m FROM MurmurTable m WHERE m.wuser = :wuser"),
@NamedQuery(name = "MurmurTable.findByMessage", query = "SELECT m FROM MurmurTable m WHERE m.message = :message"),
@NamedQuery(name = "MurmurTable.findByZoom", query = "SELECT m FROM MurmurTable m WHERE m.zoom = :zoom"),
@NamedQuery(name = "MurmurTable.findByLocation",
query = "SELECT m FROM MurmurTable m WHERE FUNCTION('ST_Distance', m.geom, :area) = 0")
})
public class MurmurTable implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Long id;
@Column(name = "wtime")
@Temporal(TemporalType.TIMESTAMP)
private Date wtime;
@Size(max = 255)
@Column(name = "wuser")
private String wuser;
@Size(max = 1023)
@Column(name = "message")
private String message;
@Column(name = "zoom")
private Integer zoom;
@Column(name = "geom", columnDefinition = "geometry") // geometry is the type of db column
@Convert(converter = com.snail.gmurmur.GeometryObjectConverter.class)
private PGgeometry geom;
public MurmurTable() {
}
public MurmurTable(Long id) {
this.id = id;
}
@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof MurmurTable)) {
return false;
}
MurmurTable other = (MurmurTable) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
@Override
public String toString() {
return "com.snail.gmurmur.MurmurTable[ id=" + id + " ]";
}
}
- Netbeans が、自動生成した Entity では、地理空間情報を格納している geom は、Object型。PGgeometry 型に変更する
- Tips : JPA 標準の型ではなく、JDBC ドライバ固有の型を使うときには、何もしない Converter が必要。Converter を設定しないと JPA が、値を Serialize しようとして、やり方がわからないとエラーを吐く。
package com.snail.gmurmur;
import javax.persistence.Converter;
import org.postgis.PGgeometry;
@Converter
public class GeometryObjectConverter implements javax.persistence.AttributeConverter<PGgeometry, PGgeometry> {
@Override
public PGgeometry convertToDatabaseColumn(PGgeometry x) {
return x;
}
@Override
public PGgeometry convertToEntityAttribute(PGgeometry y) {
return y;
}
}
Converter †
- 本来はフラグなどに使う。Entity では enum、DBMS では int でデータを持っているときに、その変換を行う
- JPA2.1 〜
FUNCTION 句 †
- JPA2.1 〜
- JPQL から DBMS 固有の関数を呼び出せるようになった FUNCTION(関数名, 引数1, 引数2, 引数3, ...)
@NamedQuery(name = "MurmurTable.findByLocation",
query = "SELECT m FROM MurmurTable m WHERE FUNCTION('ST_Distance', m.geom, :area) = 0")
RDB 固有の Operator を JPQL から使いたい †
persistence.xml †
<?xml version="1.0" encoding="UTF-8"?>
<persistence
version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="com_snail_GMurmurPU" transaction-type="JTA">
<jta-data-source>jdbc/murmur</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<shared-cache-mode>NONE</shared-cache-mode>
<properties/>
</persistence-unit>
<persistence-unit name="com_snail_GMurmurPU_test" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>com.snail.gmurmur.MurmurTable</class>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<shared-cache-mode>NONE</shared-cache-mode>
<properties>
<property name="eclipselink.target-database" value="PostgreSQL"/>
<property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost/demo"/>
<property name="javax.persistence.jdbc.user" value="postgres"/>
<property name="javax.persistence.jdbc.password" value="postgres"/>
<!-- OFF,SEVERE,WARNING,INFO,CONFIG,FINE,FINER,FINEST,ALL -->
<property name="eclipselink.logging.level" value="ALL"/>
<property name="eclipselink.logging.timestamp" value="false"/>
<property name="eclipselink.logging.session" value="false"/>
<property name="eclipselink.logging.thread" value="false"/>
</properties>
</persistence-unit>
</persistence>
SessionBean? (com.snail.gmurmur.MsgMgrBean?) †
package com.snail.gmurmur;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import javax.ejb.LocalBean;
import javax.ejb.Singleton;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import org.postgis.LinearRing;
import org.postgis.PGgeometry;
import org.postgis.Point;
import org.postgis.Polygon;
@Singleton
@LocalBean
public class MsgMgrBean {
@PersistenceContext(unitName = "com_snail_GMurmurPU")
private EntityManager em;
/**
* つぶやきを登録する.
*
* @param user ユーザ
* @param message メッセージ
* @param lon 経度
* @param lat 緯度
* @param zoom Zoomレベル
* @throws SQLException 緯度・経度が不正な場合
*/
public void registerMurmur(
final String user, final String message,
final float lon, final float lat, final int zoom) throws SQLException {
MurmurTable murmur = new MurmurTable();
murmur.setWtime(new Date());
murmur.setWuser(user);
murmur.setMessage(message);
murmur.setZoom(zoom);
PGgeometry geom = new PGgeometry(createPoint(lon, lat));
murmur.setGeom(geom);
em.persist(murmur);
}
/**
* 範囲からつぶやきを検索します.
* @param nwLon 北西経度
* @param nwLat 北西緯度
* @param neLon 北東経度
* @param neLat 北東緯度
* @param swLon 南西経度
* @param swLat 南西緯度
* @param seLon 南東経度
* @param seLat 南東緯度
* @return つぶやきのリスト
*/
public List<MurmurTable> findMurmur(
final float nwLon, final float nwLat, final float neLon, final float neLat,
final float swLon, final float swLat, final float seLon, final float seLat) {
LinearRing ring = new LinearRing(new Point[]{
createPoint(nwLon, nwLat), createPoint(neLon, neLat),
createPoint(seLon, seLat), createPoint(swLon, swLat),
createPoint(nwLon, nwLat)});
Polygon polygon = new Polygon(new LinearRing[]{ring});
polygon.setSrid(4326);
PGgeometry geom = new PGgeometry(polygon);
TypedQuery<MurmurTable> query = em.createNamedQuery("MurmurTable.findByLocation", MurmurTable.class);
query.setParameter("area", geom);
return query.getResultList();
}
/**
* 座標系4326の Point を作ります.
* @param lon 経度
* @param lat 緯度
* @return Point
*/
private Point createPoint(final float lon, final float lat) {
Point point = new Point(lon, lat);
point.setSrid(4326);
return point;
}
}
Test(com.snail.gmurmur.test.MsgWriteTest?) †
package com.snail.gmurmur.test;
import com.snail.gmurmur.MsgMgrBean;
import com.snail.gmurmur.MurmurTable;
import java.sql.SQLException;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import mockit.Deencapsulation;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.equalTo;
public class MsgWriteTest {
private static EntityManagerFactory emf;
private static EntityManager em;
private static MsgMgrBean target = new MsgMgrBean();
@BeforeClass
public static void setUpClass() {
emf = Persistence.createEntityManagerFactory("com_snail_GMurmurPU_test");
em = emf.createEntityManager();
// Injection
Deencapsulation.setField(target, "em", em);
}
@AfterClass
public static void tearDownClass() {
em.close();
emf.close();
}
@Test
public void testAddMurmur() throws SQLException {
em.getTransaction().begin();
Query cleanupQuery = em.createNativeQuery("DELETE FROM murmur_table");
cleanupQuery.executeUpdate();
target.registerMurmur("test01", "I have a pen", 135.0F, 35.0F, 12);
List<MurmurTable> res = target.findMurmur(130.0F, 40.0F, 140.0F, 40.0F, 130.0F, 30.0F, 140.0F, 30.0F);
assertThat(res.size(), is(equalTo(1)));
MurmurTable m1 = res.get(0);
assertThat(m1.getWuser(), is(equalTo("test01")));
assertThat(m1.getMessage(), is(equalTo("I have a pen")));
assertThat(m1.getGeom().toString(), is(equalTo("SRID=4326;POINT(135 35)")));
em.getTransaction().commit();
}
}
pom.xml †
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.snail</groupId>
<artifactId>GMurmur</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>GMurmur</name>
<properties>
<endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jmockit.version>1.1</jmockit.version>
</properties>
<dependencies>
<dependency>
<groupId>com.googlecode.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>${jmockit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.5.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>2.1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa.modelgen.processor</artifactId>
<version>2.5.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.postgis</groupId>
<artifactId>postgis-jdbc</artifactId>
<version>1.3.3</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.12.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.3-1100-jdbc41</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<compilerArguments>
<endorseddirs>${endorsed.dir}</endorseddirs>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<outputDirectory>${endorsed.dir}</outputDirectory>
<silent>true</silent>
<artifactItems>
<artifactItem>
<groupId>javax</groupId>
<artifactId>javaee-endorsed-api</artifactId>
<version>7.0</version>
<type>jar</type>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.14</version>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}"/com/googlecode/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar
</argLine>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
<id>eclipselink</id>
<layout>default</layout>
<name>Repository for library EclipseLink (JPA 2.1)</name>
</repository>
</repositories>
</project>
Java#Glassfish