【问题标题】:Refactoring application: Direct database access -> access through REST重构应用:直接数据库访问 -> 通过 REST 访问
【发布时间】:2013-02-07 19:26:19
【问题描述】:

我们有一个庞大的数据库应用程序,必须对其进行重构(原因有很多。最大的一个原因:安全性)。

我们已经拥有的:

  • MySQL 数据库
  • 超过 100 个表的 JPA2 (Eclipselink) 类
  • 直接访问数据库的客户端应用程序

需要具备的条件:

  • REST 接口
  • 通过数据库使用角色登录/注销

到目前为止我做了什么:

  • 使用 Spring Security 3.1.1 设置 Spring MVC 3.2.1
  • 使用自定义 UserDetailsService(仅包含用于测试 atm 的静态数据)
  • 创建了几个控制器用于测试(只是接收/提供数据)

设计问题:

  • 我们的数据库中有 maaaaany @OneToMany 和 @ManyToMany 关系

1.:(重要)

如果我发送包含所有子对象的整个对象树作为响应,我可能会一次发送整个数据库。

所以我需要一种方法来请求例如“所有文章”。但它应该省略所有子对象。我昨天试过了,我收到的对象有很多兆字节:

@PersistenceContext
private EntityManager em;


@RequestMapping(method=RequestMethod.GET)
public @ResponseBody List<Article> index() {        
    List<Article> a = em.createQuery("SELECT a FROM Article a", Article.class).getResultList(); 
    return a;
}

2.:(重要)

如果客户收到一篇文章,目前我们可以简单地调用article.getAuthor(),JPA 会调用SELECT a FROM Author a JOIN Article ar WHERE ar.author_id = ?。

使用 REST,我们可以向 /authors/{id} 发出请求。 但是:这样我们就不能在客户端使用我们旧的 JPA 模型,因为该模型包含 Author author 而不是 Long author_id。

我们必须重写每个模型还是有更简单的方法?

3.:(不太重要)

身份验证:是否使其无状态?到目前为止,我从未使用过无状态身份验证,但 Spring 似乎对它有某种支持。当我查看网络上的一些示例实现时,我会担心安全问题:每次请求时,他们都会发送用户名和密码。这不可能是正确的方法。

如果有人知道一个很好的解决方案,请告诉我。否则我只会使用标准 HTTP 会话。

4.:

设计客户端模型的最佳方式是什么?

public class Book {
    int id;

    List<Author> authors; //option1
    List<Integer> authorIds; //option2
    Map<Integer, Author> idAuthorMap; //option3
}

(这是一本有多个作者的书)。这三个选项各有优缺点:

  1. 我可以直接访问对应的Author 模型,但是如果我通过 REST 请求一个Book 模型,我现在可能不想要该模型,但稍后。所以选项 2 会更好:
  2. 我可以直接通过 REST 请求 Book 模型。然后使用authorIds 获取相应的作者。但现在我不能简单地使用myBook.getAuthors()。
  3. 这是 1. 和 2. 的混合体:如果我只请求 Books 并且只包含 Author id,我可以执行以下操作:idAuthorMap.put(authorId, null)。

但也许有一个 Java 库可以为我处理所有事情?!


现在就是这样。谢谢各位:)


可能的解决方案:

问题:只选择我需要的数据。这意味着或多或少地忽略每个@ManyToMany、@OneToMany、@ManyToOne 关系。

解决方案:使用@JsonIgnore 和/或@JsonIgnoreProperties。


问题:每个被忽略的关系都应该在不修改数据模型的情况下轻松获取。

解决方案:示例模型:

class Book {
  int bId;
  Author author; // has @ManyToOne
}

class Author {
  int aId;
  List<Book> books; // has @OneToMany
}

现在我可以通过 REST 获取一本书:GET /books/4,结果将如下所示(因为我通过 @JsonIgnore 忽略所有关系):{"bId":4}

然后我必须创建另一个路由来接收相关作者:GET /books/4/author。将返回:{"aId":6}。

向后:GET /authors/6/books -> [{"bId":4},{"bId":42}]。

每个@ManyToMany、@OneToMany、@ManyToOne 都会有一条路线,但仅此而已。所以这个不会存在:GET /authors/6/books/42。客户端应使用GET /books/42。

【问题讨论】:

    标签: rest jpa spring-mvc jpa-2.0 eclipselink


    【解决方案1】:

    首先,您需要控制 JPA 层如何处理您的关系。我的意思是使用延迟加载与急切加载。这可以通过注释上的“fetch”选项轻松控制,如下所示:

    @OneToMany(fetch=FetchType.Lazy)
    

    这告诉 JPA,对于这个相关对象,只有在某些代码请求它时才加载它。在幕后,正在制作/创建一个动态“代理”对象。当您尝试访问此代理时,它足够聪明,可以出去执行另一个 SQL 来收集所需的位。在 Collection 的情况下,如果您迭代 Collection 中的项目,它甚至足够聪明地批量抓取底层对象。但是,请注意:对这些代理的访问必须在同一个常规会话中进行。底层 ORM 框架(不知道 Eclipselink 是如何工作的……我是 Hybernate 用户)不知道如何将子请求与正确的域对象相关联。当您使用像 Flex BlazeDS 这样的传输框架时,这会产生更大的影响,它会尝试使用字节码而不是接口来编组对象,并且通常会在看到这些代理对象时出错。

    您可能还想设置您的级联策略,这可以通过“级联”选项来完成,例如

    @OneToMany(cascade=CascadeType.ALL)
    

    或者你可以给它一个类似的列表:

    @OneToMany(cascade={CascadeType.MERGE, CascadeType.REMOVE})
    

    一旦您控制了从数据库中提取的内容,您就需要查看如何编组域对象。您是否根据请求通过 JSON、XML 或混合方式发送此信息?您使用什么框架(Jackson、FlexJSON、XStream 等)?问题是,即使您将 fetch 类型设置为 Lazy,这些框架仍然会跟踪相关对象,从而否定您所做的所有工作告诉它延迟加载。这就是编组/序列化方案更具体的地方:你需要弄清楚如何告诉你的框架什么是编组,什么是不编组。同样,这将高度依赖于正在使用的任何框架。

    【讨论】:

    • 您好,感谢您的详细回答!我使用 Spring MVC 3.2.1 和 Jackson 2。如果有一个 HTTP 请求想要接收 application/json,Spring 会自动检测 Jackson 并使用它。现在我有另一个(对我来说)难题:如何在客户端设计模型? (我将在我的问题底部更详细地解释它)
    • 我认为您不需要创建数据转换对象。您应该能够简单地修改 Jackson 如何使用您现有的域对象。这是几年前的一篇文章,但它展示了一种自定义 Jackson 如何序列化/编组对象的方法:texscribbles.blogspot.com/2010/07/…
    • 至于您的安全问题,我假设您正在从网页使用 AJAX?如果是这样,那么简单的答案是让用户通过网站进行身份验证,然后对您的应用服务器的所有请求都将使用该身份验证。 AJAX 调用只不过是一个简单的 HTTP 查询。但是,如果您有未经身份验证的客户端使用这些方法,您将不得不使用基本身份验证或其他一些机制(单点登录或一些自定义应用程序密钥机制)来对请求进行身份验证和授权。
    • 至于无状态身份验证,可能还有其他解决方案。 OAuth 或其他一些授权框架(例如用于 SSO 的 SiteMinder)可能值得研究。
    • 谢谢。我去看看杰克逊的网站。 ... 关于身份验证:目前我们有 Java/Swing 客户端,但今年晚些时候将会有第二个使用 HTML/JavaScript 的客户端。但这些 HTML 客户端将是某种带有嵌入式 firefox 的 Java 应用程序,它作为独立应用程序提供,因此使用 JSONP。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-06
    • 1970-01-01
    • 1970-01-01
    • 2013-07-01
    • 2011-01-09
    相关资源
    最近更新 更多