que - mostrar datos de una base de datos en java
¿Cómo crear una relación de uno a muchos con la API de objetos SQL de JDBI? (3)
Hay una vieja publicación de grupos de google donde Brian McAllistair (uno de los autores de JDBI) hace esto mapeando cada fila unida a un objeto interino, y luego dobla las filas en el objeto de destino.
Vea la discusión aquí . Aquí hay un código de prueba .
Personalmente, esto parece un poco insatisfactorio, ya que significa escribir un objeto DBO adicional y mapeador para la estructura provisional. ¡Todavía creo que esta respuesta debería incluirse para completar!
Estoy creando una aplicación REST simple con dropwizard usando JDBI. El siguiente paso es integrar un nuevo recurso que tiene una relación de uno a muchos con otro. Hasta ahora no podía encontrar la manera de crear un método en mi DAO que recuperara un solo objeto que contiene una lista de objetos de otra tabla.
Las representaciones de POJO serían algo como esto:
public class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Account {
private int id;
private String name;
private List<User> users;
public Account(int id, String name, List<User> users) {
this.id = id;
this.name = name;
this.users = users;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<User> getUsers() {
return name;
}
public void setUsers(List<Users> users) {
this.users = users;
}
}
El DAO debería verse algo como esto
public interface AccountDAO {
@Mapper(AccountMapper.class)
@SqlQuery("SELECT Account.id, Account.name, User.name as u_name FROM Account LEFT JOIN User ON User.accountId = Account.id WHERE Account.id = :id")
public Account getAccountById(@Bind("id") int id);
}
Pero cuando el método tiene un único objeto como valor de retorno ( Cuenta en lugar de Lista <Cuenta> ), parece que no hay forma de acceder a más de una línea del conjunto de resultados en la clase Mapeador. La única solución que puedo encontrar se describe en https://groups.google.com/d/msg/jdbi/4e4EP-gVwEQ/02CRStgYGtgJ pero esa también solo devuelve un conjunto con un solo objeto que no parece muy elegante. (Y no puede ser utilizado adecuadamente por las clases de recursos).
Parece haber una manera de usar una carpeta2 en la API fluida. Pero no sé cómo integrarlo correctamente con dropwizard y prefiero apegarme a la API de objeto SQL de JDBI como se recomienda en la documentación del asistente de instalación.
¿Realmente no hay forma de obtener una asignación de uno a muchos utilizando la API de objeto SQL en JDBI? Ese es un caso de uso tan básico para una base de datos que creo que me falta algo.
Toda ayuda es muy apreciada,
Tilman
OK, después de mucha búsqueda, veo dos formas de lidiar con esto:
La primera opción es recuperar un objeto para cada columna y fusionarlo en el código de Java en el recurso (es decir, hacer la unión en el código en lugar de tenerlo hecho por la base de datos). Esto daría como resultado algo así como
@GET
@Path("/{accountId}")
public Response getAccount(@PathParam("accountId") Integer accountId) {
Account account = accountDao.getAccount(accountId);
account.setUsers(userDao.getUsersForAccount(accountId));
return Response.ok(account).build();
}
Esto es factible para operaciones de unión más pequeñas, pero no parece muy elegante para mí, ya que esto es algo que la base de datos debe hacer. Sin embargo, decidí tomar este camino ya que mi aplicación es bastante pequeña y no quería escribir mucho código de mapeo.
La segunda opción es escribir un asignador, que recupera el resultado de la consulta de combinación y lo mapea en el objeto de esta manera:
public class AccountMapper implements ResultSetMapper<Account> {
private Account account;
// this mapping method will get called for every row in the result set
public Account map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
// for the first row of the result set, we create the wrapper object
if (index == 0) {
account = new Account(rs.getInt("id"), rs.getString("name"), new LinkedList<User>());
}
// ...and with every line we add one of the joined users
User user = new User(rs.getInt("u_id"), rs.getString("u_name"));
if (user.getId() > 0) {
account.getUsers().add(user);
}
return account;
}
}
La interfaz DAO tendrá un método como este:
public interface AccountDAO {
@Mapper(AccountMapper.class)
@SqlQuery("SELECT Account.id, Account.name, User.id as u_id, User.name as u_name FROM Account LEFT JOIN User ON User.accountId = Account.id WHERE Account.id = :id")
public List<Account> getAccountById(@Bind("id") int id);
}
Nota: Su clase DAO abstracta compilará silenciosamente si usa un tipo de devolución que no sea de colección, por ejemplo, public Account getAccountById(...);
. Sin embargo, su asignador solo recibirá un conjunto de resultados con una sola fila, incluso si la consulta SQL hubiera encontrado varias filas, que su asignador fácilmente convertirá en una única cuenta con un solo usuario. JDBI parece imponer un LIMIT 1
para consultas SELECT
que tienen un tipo de devolución que no es de colección. Es posible poner métodos concretos en su DAO si lo declara como una clase abstracta, por lo que una opción es resumir la lógica con un par de métodos público / protegido, como sigue:
public abstract class AccountDAO {
@Mapper(AccountMapper.class)
@SqlQuery("SELECT Account.id, Account.name, User.id as u_id, User.name as u_name FROM Account LEFT JOIN User ON User.accountId = Account.id WHERE Account.id = :id")
protected abstract List<Account> _getAccountById(@Bind("id") int id);
public Account getAccountById(int id) {
List<Account> accountList = _getAccountById(id);
if (accountList == null || accountList.size() < 1) {
// Log it or report error if needed
return null;
}
// The mapper will have given a reference to the same value for every entry in the list
return accountList.get(accountList.size() - 1);
}
}
Esto todavía me parece un poco engorroso y de bajo nivel, ya que generalmente hay muchas combinaciones para trabajar con datos relacionales. Me encantaría ver una mejor manera o hacer que JDBI apoye una operación abstracta para esto con la API de objeto SQL.
Tengo una pequeña biblioteca que será muy útil para mantener una relación de uno a uno y uno a uno. También proporciona más funciones para los mapeadores predeterminados.