1. 简介
本文是《OData 协议指南》的后续内容,我们将基于 OData 协议,使用 Apache Olingo 实现一个简单的 OData 服务。
Olingo 是 Java 平台下主流的 OData 实现之一,它提供了一套框架,可以让我们以标准方式暴露数据库中的数据。通过 Olingo,我们可以轻松实现 CRUD 操作,并支持 OData 协议中的分页、排序、过滤等特性。
2. Olingo 简介
Olingo 是 Apache 基金会维护的一套 OData 实现框架,主要包含以下三个模块:
- Java V2:支持 OData V2 的客户端与服务端库
- Java V4:仅支持 OData V4 的服务端库
- JavaScript V4:面向浏览器的客户端库,仅支持 OData V4
本文将重点讲解 Java V2 模块的使用。相比 V4,V2 更加成熟,支持 JPA 集成,开发效率更高。而 V4 则更底层,需要开发者自行处理元数据生成、URL 参数解析、后端查询构造等细节。
3. 实现一个 Olingo V2 服务
我们将使用 Spring Boot + Olingo V2 + JPA 实现一个简单的 OData 服务,包含两个实体:CarMaker
和 CarModel
。
3.1. 依赖配置
在 pom.xml
中添加以下 Olingo 相关依赖:
<dependency>
<groupId>org.apache.olingo</groupId>
<artifactId>olingo-odata2-core</artifactId>
<version>2.0.11</version>
<exclusions>
<exclusion>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.olingo</groupId>
<artifactId>olingo-odata2-jpa-processor-core</artifactId>
<version>2.0.11</version>
</dependency>
<dependency>
<groupId>org.apache.olingo</groupId>
<artifactId>olingo-odata2-jpa-processor-ref</artifactId>
<version>2.0.11</version>
<exclusions>
<exclusion>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
</exclusion>
</exclusions>
</dependency>
⚠️ 注意:Olingo 默认依赖 EclipseLink,但我们使用 Spring Boot,需要排除这部分依赖以避免冲突。
3.2. 定义领域模型
我们定义两个实体类:CarMaker
和 CarModel
,它们之间是一对多关系:
@Entity
@Table(name = "car_maker")
public class CarMaker {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
private String name;
@OneToMany(mappedBy = "maker", orphanRemoval = true, cascade = CascadeType.ALL)
private List<CarModel> models;
// getters, setters 省略
}
@Entity
@Table(name = "car_model")
public class CarModel {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@NotNull
private String name;
@NotNull
private Integer year;
@NotNull
private String sku;
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "maker_fk")
private CarMaker maker;
// getters, setters 省略
}
3.3. 实现 ODataJPAServiceFactory
Olingo 通过 ODataJPAServiceFactory
与 JPA 集成。我们需要实现其 initializeODataJPAContext()
方法,提供一个 EntityManager
:
@Component
public class CarsODataJPAServiceFactory extends ODataJPAServiceFactory {
@Override
public ODataJPAContext initializeODataJPAContext() throws ODataJPARuntimeException {
ODataJPAContext ctx = getODataJPAContext();
ODataContext octx = ctx.getODataContext();
HttpServletRequest request = (HttpServletRequest) octx.getParameter(ODataContext.HTTP_SERVLET_REQUEST_OBJECT);
EntityManager em = (EntityManager) request.getAttribute(EntityManagerFilter.EM_REQUEST_ATTRIBUTE);
ctx.setEntityManager(em);
ctx.setPersistenceUnitName("default");
ctx.setContainerManaged(true);
return ctx;
}
}
✅ 关键点:我们没有使用
setEntityManagerFactory()
,而是直接传入EntityManager
,避免与 Spring 的事务管理冲突。
3.4. 配置 Jersey 资源
我们需要注册 Olingo 的入口,并替换默认的 ODataRootLocator
,以支持 Spring 管理的 ServiceFactory
:
@Component
@ApplicationPath("/odata")
public class JerseyConfig extends ResourceConfig {
public JerseyConfig(CarsODataJPAServiceFactory serviceFactory, EntityManagerFactory emf) {
ODataApplication app = new ODataApplication();
app.getClasses().forEach(c -> {
if (!ODataRootLocator.class.isAssignableFrom(c)) {
register(c);
}
});
register(new CarsRootLocator(serviceFactory));
register(new EntityManagerFilter(emf));
}
}
自定义 CarsRootLocator
:
@Path("/")
public class CarsRootLocator extends ODataRootLocator {
private final CarsODataJPAServiceFactory serviceFactory;
public CarsRootLocator(CarsODataJPAServiceFactory serviceFactory) {
this.serviceFactory = serviceFactory;
}
@Override
public ODataServiceFactory getServiceFactory() {
return this.serviceFactory;
}
}
3.5. 注入 EntityManager 的 Filter
为了在请求中使用 EntityManager
,我们创建一个 ContainerRequestFilter
和 ContainerResponseFilter
来管理事务:
@Provider
public static class EntityManagerFilter implements ContainerRequestFilter, ContainerResponseFilter {
public static final String EM_REQUEST_ATTRIBUTE = EntityManagerFilter.class.getName() + "_ENTITY_MANAGER";
private final EntityManagerFactory emf;
@Context
private HttpServletRequest httpRequest;
public EntityManagerFilter(EntityManagerFactory emf) {
this.emf = emf;
}
@Override
public void filter(ContainerRequestContext ctx) throws IOException {
EntityManager em = this.emf.createEntityManager();
httpRequest.setAttribute(EM_REQUEST_ATTRIBUTE, em);
if (!"GET".equalsIgnoreCase(ctx.getMethod())) {
em.getTransaction().begin();
}
}
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
EntityManager em = (EntityManager) httpRequest.getAttribute(EM_REQUEST_ATTRIBUTE);
if (!"GET".equalsIgnoreCase(requestContext.getMethod())) {
EntityTransaction t = em.getTransaction();
if (t.isActive() && !t.getRollbackOnly()) {
t.commit();
}
}
em.close();
}
}
3.6. 测试接口
使用 curl
可以快速测试服务:
# 获取元数据
curl http://localhost:8080/odata/$metadata
# 获取实体集合
curl http://localhost:8080/odata/CarMakers
curl http://localhost:8080/odata/CarModels
# 获取单个实体
curl http://localhost:8080/odata/CarMakers(1)
curl http://localhost:8080/odata/CarModels(1)
# 获取关联数据
curl http://localhost:8080/odata/CarModels(1)/CarMakerDetails
# 过滤查询
curl http://localhost:8080/odata/CarMakers?$filter=startswith(Name,'B')
4. 总结
本文通过 Spring Boot + Olingo V2 + JPA 实现了一个完整的 OData 服务,支持标准的 OData 协议操作,包括查询、过滤、分页等。
虽然 Olingo V4 更符合当前 OData 协议的发展方向,但其 JPA 集成尚不完善。目前官方的 OLINGO-549 issue 已停滞多年,社区也出现了如 SAP/olingo-jpa-processor-v4 这样的第三方实现,但尚未正式发布。
如果你希望快速构建一个生产级的 OData 服务,Olingo V2 仍然是一个简单粗暴且稳定的选择。
✅ 所有源码已上传至 GitHub 仓库,欢迎 star & fork。