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 服务,包含两个实体:CarMakerCarModel

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. 定义领域模型

我们定义两个实体类:CarMakerCarModel,它们之间是一对多关系:

@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,我们创建一个 ContainerRequestFilterContainerResponseFilter 来管理事务:

@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。


Olingo Request Processing