2014-06-20 111 views
22

我使用JDBI創建了一個簡單的REST應用程序和dropwizard。下一步是整合一個與另一個具有一對多關係的新資源。直到現在我都無法弄清楚如何在我的DAO中創建一個方法來檢索一個包含另一個表中對象列表的對象。如何使用JDBI SQL對象API創建一對多關係?

的POJO的描述是這樣的:

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; 
    } 

} 

的DAO應該是這個樣子

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); 

} 

但當方法有一個對象作爲返回值(帳戶代替列表<帳戶>)似乎無法訪問Mapper類中resultSet的多行。我能找到的唯一解決方案是在https://groups.google.com/d/msg/jdbi/4e4EP-gVwEQ/02CRStgYGtgJ中描述的,但那個解決方案也只能返回一個Set,其中有一個看起來不太優雅的對象。 (並且不能被資源類正確使用。)

似乎有一種方法在流利的API中使用了Folder2。但我不知道如何正確地將它與dropwizard集成在一起,而且我更願意按照dropwizard文檔中的建議堅持JDBI的SQL對象API。

真的沒有辦法在JDBI中使用SQL對象API獲得一對多映射嗎?對於一個數據庫來說,這是一個基本的用例,我認爲我必須錯過一些東西。

所有幫助是極大的讚賞,
   蒂爾曼

回答

27

OK,很多搜索後,我看到了兩個辦法處理這個:

第一個選項被檢索的對象每列並將其合併到資源中的Java代碼中(即在代碼中進行連接,而不是由數據庫完成)。 這將導致類似

@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(); 
} 

這是可行的較小的聯接操作,但似乎不是很優雅我,因爲這是後話了數據庫應該做的事。但是,我決定採用這種方式,因爲我的應用程序非常小,我不想寫很多映射器代碼。

第二個選項是寫一個映射器,檢索​​連接查詢的結果,並且將其映射到這樣的對象:

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; 
    } 
} 

的DAO接口然後將有這樣的方法:

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); 

} 

注:你的抽象DAO類會悄悄編譯,如果你使用非集合返回類型,如public Account getAccountById(...);。但是,即使SQL查詢可能找到多行,您的映射器也將僅接收一行結果集,您的映射器將很樂意使用單個用戶轉換爲單個帳戶。JDBI似乎強加LIMIT 1SELECT具有非收集返回類型的查詢。它有可能把具體的方法在你的DAO,如果你將它聲明爲一個抽象類,所以一個選項是收官之邏輯與公共/保護方法對,就像這樣:

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); 
    } 
} 

這似乎仍然是一個對我來說有點繁瑣和低級,因爲通常有很多聯接處理關係數據。我希望看到更好的方法,或者讓JDBI支持對SQL對象API的抽象操作。

+1

您可以通過簡單地將其抽象爲第一個選項移動到DAO類。看到這個答案:http://stackoverflow.com/a/26831146/846644 – Natan

+0

這是一個非常好的mapper技巧!我在DAO方法中添加了一個註釋和一個解決列表/非列表返回類型的方法,因爲它引起了我的不安。 –

+0

這裏值得注意的是,第二個選項可能根本不是線程安全的(由於父母Account對象),除非您知道JDBI每次執行查詢時都使用此ResultSetMapper的新實例,我真的不知道,但仍然認爲依賴脆弱的假設。 – Hesham

0

有一個老谷歌組張貼其中布賴恩McAllistair(其中一個JDBI作者)通過映射每個接合行的臨時對象,然後摺疊行插入到目標對象執行此操作。

See the discussion hereThere's test code here

就個人而言,這似乎有點不令人滿意,因爲它意味着爲臨時結構編寫額外的DBO對象和映射器。我仍然認爲這個答案應該包括完整性!