【问题标题】:Use Prepared Statement with Dropwizard JDBI and PostgreSQL UUID column使用带有 Dropwizard JDBI 和 PostgreSQL UUID 列的 Prepared Statement
【发布时间】:2014-07-26 21:18:09
【问题描述】:

快速概述,我正在使用 Java Web 框架 Dropwizard 和 PostgreSQL 数据库来学习如何创建 API。 Dropwizard 提供了 JDBI JDBC 包装器来与数据库交互。

TLDR:需要将原生 Postgres UUID 类型与 JDBI 绑定。

SRC:https://github.com/jasonshell/hooktale-api

我遇到的问题是 JDBI 需要 ResultSetMapper 或 ResultSetMapperFactory 以获得更复杂的映射。在我的情况下,这涉及为angler_id拥有一个PostgreSQL uuid。在 AnglerMapper.java 中,rs.getObject("angler_id", java.util.UUID.class) 抛出了一个未在 JDBC 中实现的方法错误。

AnglerMapper.java(导致错误)

package org.sachmo.hooktale.db;

import org.sachmo.hooktale.core.Angler;
import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.tweak.ResultSetMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Created by jason on 5/27/14.
 * angler mapper
 */
public class AnglerMapper implements ResultSetMapper<Angler> {
  @Override

  public Angler map(int i, ResultSet rs, StatementContext statementContext) throws         SQLException {
    return new Angler(rs.getObject("angler_id", java.util.UUID.class),   rs.getString("firstName"), rs.getString("lastName"), rs.getString("email"), rs.getString("password"));
  }
}

经过一番搜索,我了解到接受 UUID 参数进行绑定的能力不是 JDBI 的属性,而是底层 JDBC 驱动程序。在我的例子中是 org.postgresql.9.3-1100-jdbc41,它能够使用 PreparedStatement.setObject() 接受 UUID,并导致 UUID 的本机绑定在没有 ArgumentFactory 的情况下工作。

我的问题是我不知道如何/在哪里正确实施 PreparedStatement.setObject()。任何关于接受 UUID 参数以与 JDBI 绑定的建议将不胜感激。

如果需要更多信息,包括支持类/接口。

Angler.java

package org.sachmo.hooktale.core;

import io.dropwizard.jackson.JsonSnakeCase;

import java.util.UUID;

/**
 * Created by jason on 5/20/14.
 * angler
 */
@JsonSnakeCase
public class Angler {
  private UUID id;
  private String firstName;
  private String lastName;
  private String email;
  private String password;

  public Angler(UUID id, String firstName, String lastName, String email, String password) {
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
    this.email = email;
    this.password = password;
  }

  public UUID getId() {
    return id;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  public String getEmail() {
    return email;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public String getPassword() {
    return password;
  }
}

AnglerDAO.java

package org.sachmo.hooktale.db;

import org.sachmo.hooktale.core.Angler;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;

import java.util.List;
import java.util.UUID;

/**
 * Created by jason on 5/20/14.
 * angler dao
 */
@RegisterMapper(AnglerMapper.class)
public interface AnglerDAO {
  @SqlQuery("SELECT * FROM angler ORDER BY created_at")
  List<Angler> findAll();

  @SqlQuery("SELECT * FROM angler WHERE id = :it")
  Angler findById(@Bind UUID id);
}

AnglerResource.java

package org.sachmo.hooktale.resources;

import com.codahale.metrics.annotation.Timed;
import org.sachmo.hooktale.core.Angler;
import org.sachmo.hooktale.db.AnglerDAO;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.UUID;

/**
 * Created by jason on 5/20/14.
 * angler resource
 */
@Path("/angler")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class AnglerResource {
  private final AnglerDAO anglerDAO;

  public AnglerResource(AnglerDAO anglerDAO) {
    this.anglerDAO = anglerDAO;
  }

  @Path("/{id}")
  @GET
  @Timed
  public Angler getAngler(@PathParam("id")UUID uuid) {
    Angler angler = anglerDAO.findById(uuid);
    if (angler != null) {
      return angler;
    } else {
      throw new WebApplicationException(Response.Status.NOT_FOUND);
    }
  }

  @GET
  @Timed
  public List<Angler> listAngler() {
    return anglerDAO.findAll();
  }
}

【问题讨论】:

  • 您不想添加数字主键字段并使用它来标识您的对象的任何特殊原因?
  • @condit 在大多数情况下,它只是学习如何使用 JDBI 从 Postgres 中获取 UUID。一个根本原因是我已经读到,与随机 ID 相比,使用数字递增的主键字段(如 BigInt)会使您的应用程序处于易受攻击的位置。例如,如果您有 example.com/users/1000 并且攻击者绕过授权,那么他们可以更容易地枚举用户,而不是随机 UUID。

标签: java postgresql jdbc uuid dropwizard


【解决方案1】:

感谢Steven Schlansker,有人指出我在尝试使用 PreparedStatement.setObject 时遇到困难的原因是它不是解决问题的正确方法。

AnglerMapper 需要从 Postgres 获取 UUID,而不是设置它。重新审视我收到的错误:

java.sql.SQLFeatureNotSupportedException:方法 org.postgresql.jdbc4.Jdbc4ResultSet.getObject(int, Class) 不是 尚未实施。

Steven grep 了 JDBC 源并确认它不支持该方法,但它确实支持使用 (UUID)rs.getObject(1) 获取 UUID。这与我最初尝试的 rs.getObject(1, UUID.class) 接近。

JDBC 异常

public <T> T getObject(int columnIndex, Class<T> type) throws SQLException { 
    throw org.postgresql.Driver.notImplemented(this.getClass(), "getObject(int, Class<T>)"); 
}

org/postgresql/test/jdbc4/UUIDTest.java

public void testUUID() throws SQLException {
    UUID uuid = UUID.randomUUID();
    PreparedStatement ps = _conn.prepareStatement("INSERT INTO uuidtest VALUES (?)");
    ps.setObject(1, uuid, Types.OTHER);
    ps.executeUpdate();
    ps.close();

    Statement stmt = _conn.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT id FROM uuidtest");
    assertTrue(rs.next());

    UUID uuid2 = (UUID)rs.getObject(1);
    assertEquals(uuid, rs.getObject(1));
    assertEquals(uuid.toString(), rs.getString(1));

    rs.close();
    stmt.close();
}

AnglerMapper 的更正和工作版本:

AnglerMapper.java

package org.sachmo.hooktale.db;

import org.sachmo.hooktale.core.Angler;
import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.tweak.ResultSetMapper;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;

/**
 * Created by jason on 5/27/14.
 * angler mapper
 */
public class AnglerMapper implements ResultSetMapper<Angler> {
  @Override
  public Angler map(int i, ResultSet rs, StatementContext statementContext) throws     SQLException {
    return new Angler((UUID)rs.getObject("angler_id"), rs.getString("first_name"),
            rs.getString("last_name"), rs.getString("email"), rs.getString("password"));
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多