更新:2015 年 10 月 4 日。我通常不再做任何这种行映射。您可以通过注释更优雅地完成选择性 JSON 表示。请参阅此要点。
我花了一整天的大部分时间试图为我的 3 层嵌套对象解决这个问题,并最终确定了它。这是我的情况:
帐户(即用户)--1tomany--> 角色--1tomany--> 视图(允许用户查看)
(这些 POJO 类粘贴在最底部。)
我希望控制器返回一个像这样的对象:
[ {
"id" : 3,
"email" : "catchall@sdcl.org",
"password" : "sdclpass",
"org" : "Super-duper Candy Lab",
"role" : {
"id" : 2,
"name" : "ADMIN",
"views" : [ "viewPublicReports", "viewAllOrders", "viewProducts", "orderProducts", "viewOfferings", "viewMyData", "viewAllData", "home", "viewMyOrders", "manageUsers" ]
}
}, {
"id" : 5,
"email" : "catchall@stereolab.com",
"password" : "stereopass",
"org" : "Stereolab",
"role" : {
"id" : 1,
"name" : "USER",
"views" : [ "viewPublicReports", "viewProducts", "orderProducts", "viewOfferings", "viewMyData", "home", "viewMyOrders" ]
}
}, {
"id" : 6,
"email" : "catchall@ukmedschool.com",
"password" : "ukmedpass",
"org" : "University of Kentucky College of Medicine",
"role" : {
"id" : 2,
"name" : "ADMIN",
"views" : [ "viewPublicReports", "viewAllOrders", "viewProducts", "orderProducts", "viewOfferings", "viewMyData", "viewAllData", "home", "viewMyOrders", "manageUsers" ]
}
} ]
一个关键点是要意识到 Spring 不会自动为您完成所有这些工作。如果你只是要求它返回一个 Account 项目而不做嵌套对象的工作,你只会得到:
{
"id" : 6,
"email" : "catchall@ukmedschool.com",
"password" : "ukmedpass",
"org" : "University of Kentucky College of Medicine",
"role" : null
}
因此,首先,创建您的 3 表 SQL JOIN 查询并确保您获得了所需的所有数据。这是我的,它出现在我的控制器中:
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
@RequestMapping("/accounts")
public List<Account> getAllAccounts3()
{
List<Account> accounts = jdbcTemplate.query("SELECT Account.id, Account.password, Account.org, Account.email, Account.role_for_this_account, Role.id AS roleid, Role.name AS rolename, role_views.role_id, role_views.views FROM Account JOIN Role on Account.role_for_this_account=Role.id JOIN role_views on Role.id=role_views.role_id", new AccountExtractor() {});
return accounts;
}
请注意,我正在加入 3 个表。现在创建一个 RowSetExtractor 类来将嵌套对象放在一起。上面的例子显示了 2 层嵌套......这个更进一步,做了 3 层。请注意,我还必须在地图中维护第二层对象。
public class AccountExtractor implements ResultSetExtractor<List<Account>>{
@Override
public List<Account> extractData(ResultSet rs) throws SQLException, DataAccessException {
Map<Long, Account> accountmap = new HashMap<Long, Account>();
Map<Long, Role> rolemap = new HashMap<Long, Role>();
// loop through the JOINed resultset. If the account ID hasn't been seen before, create a new Account object.
// In either case, add the role to the account. Also maintain a map of Roles and add view (strings) to them when encountered.
Set<String> views = null;
while (rs.next())
{
Long id = rs.getLong("id");
Account account = accountmap.get(id);
if(account == null)
{
account = new Account();
account.setId(id);
account.setPassword(rs.getString("password"));
account.setEmail(rs.getString("email"));
account.setOrg(rs.getString("org"));
accountmap.put(id, account);
}
Long roleid = rs.getLong("roleid");
Role role = rolemap.get(roleid);
if(role == null)
{
role = new Role();
role.setId(rs.getLong("roleid"));
role.setName(rs.getString("rolename"));
views = new HashSet<String>();
rolemap.put(roleid, role);
}
else
{
views = role.getViews();
views.add(rs.getString("views"));
}
views.add(rs.getString("views"));
role.setViews(views);
account.setRole(role);
}
return new ArrayList<Account>(accountmap.values());
}
}
这给出了所需的输出。以下 POJO 供参考。请注意 Role 类中的 @ElementCollection Set 视图。这就是自动生成 SQL 查询中引用的 role_views 表的原因。知道该表的存在、其名称和字段名称对于正确执行 SQL 查询至关重要。必须知道这感觉是错误的......似乎这应该更自动化 - 这不是 Spring 的用途吗?......但我想不出更好的方法。据我所知,在这种情况下,您必须手动完成工作。
@Entity
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private long id;
@Column(unique=true, nullable=false)
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String org;
private String phone;
@ManyToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "roleForThisAccount") // @JoinColumn means this side is the *owner* of the relationship. In general, the "many" side should be the owner, or so I read.
private Role role;
public Account() {}
public Account(String email, String password, Role role, String org)
{
this.email = email;
this.password = password;
this.org = org;
this.role = role;
}
// getters and setters omitted
}
@Entity
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private long id; // required
@Column(nullable = false)
@Pattern(regexp="(ADMIN|USER)")
private String name; // required
@Column
@ElementCollection(targetClass=String.class)
private Set<String> views;
@OneToMany(mappedBy="role")
private List<Account> accountsWithThisRole;
public Role() {}
// constructor with required fields
public Role(String name)
{
this.name = name;
views = new HashSet<String>();
// both USER and ADMIN
views.add("home");
views.add("viewOfferings");
views.add("viewPublicReports");
views.add("viewProducts");
views.add("orderProducts");
views.add("viewMyOrders");
views.add("viewMyData");
// ADMIN ONLY
if(name.equals("ADMIN"))
{
views.add("viewAllOrders");
views.add("viewAllData");
views.add("manageUsers");
}
}
public long getId() { return this.id;}
public void setId(long id) { this.id = id; };
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
public Set<String> getViews() { return this.views; }
public void setViews(Set<String> views) { this.views = views; };
}