重构应用程序:直接数据库访问 - >通过REST访问

时间:2013-02-22 08:23:08

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

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

我们已有的内容:

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

需要做什么:

  • REST界面
  • 通过数据库登录/注销角色

到目前为止我做了什么:

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

设计问题:

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

:(重要)

如果我将所有子对象作为响应发送整个对象树,我可能会立即发送整个数据库。

所以我需要一种方法来请求例如'所有文章'。但它应该省略所有子对象。我昨天试过这个,我收到的对象是几兆字节:

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

:(重要)

如果客户收到文章,我们现在只需致电article.getAuthor(),JPA就会SELECT a FROM Author a JOIN Article ar WHERE ar.author_id = ?

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

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

:(不太重要)

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

如果有人知道一个很好的解决方案,请告诉我。另外,我会选择标准的HTTP会话。

4:

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

public class Book {
    int id;

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

(这是一本有多位作者的书)。这三种选择都有不同的优点和缺点:

  1. 如果我通过REST请求Author模型,我可以直接访问相应的Book模型,,我可能现在不想要模型,但稍后。所以选项2会更好:
  2. 我可以直接通过REST请求Book模型。然后使用authorIds来获取相应的作者。但现在我不能简单地使用myBook.getAuthors()
  3. 这是1.和2的混合:如果我只是请求Book仅包含Author ID,我可以执行以下操作:idAuthorMap.put(authorId, null)
  4. 但也许有一个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 - &gt; [{"bId":4},{"bId":42}]

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

1 个答案:

答案 0 :(得分:1)

首先,您需要控制JPA图层处理关系的方式。我的意思是使用Lazy Loading与Eager加载。这可以很容易地通过注释上的“获取”选项控制,如:

@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,这些框架仍然会追踪相关对象,从而否定了你所做的所有工作,告诉它懒惰加载。这是编辑/序列化方案更具体的地方:你需要弄清楚如何告诉你的框架什么是编组和什么不编组。同样,这将高度依赖于正在使用的任何框架。