使用Spring Data Rest时暴露所有ID

39
我希望使用Spring Rest接口公开所有ID。
我知道默认情况下,像这样的ID不会通过rest接口公开:
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(unique=true, nullable=false)
    private Long id;

我知道可以使用这个来公开User的ID:

@Configuration
public class RepositoryConfig extends RepositoryRestMvcConfiguration {
    @Override
    protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(User.class);
    }
}

是否有一种简单的方法可以在不手动维护列表的情况下公开所有ID?


请查看这里以查看一些有用的示例,了解如何公开所有实体的标识符,或者仅公开扩展或实现特定超类或接口,或带有某些特定注释的实体的标识符。 - lcnicolau
15个回答

44

如果您想为所有实体类公开 id 字段:

import java.util.stream.Collectors;

import javax.persistence.EntityManager;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;

@Configuration
public class MyRepositoryRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {

    @Autowired
    private EntityManager entityManager;

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(entityManager.getMetamodel().getEntities().stream().map(e -> e.getJavaType()).collect(Collectors.toList()).toArray(new Class[0]));
    }

}

4
映射到实体类型:.map(EntityType::getJavaType) - 윤현구
1
这应该是被接受的答案,因为手动配置只适用于最小的使用情况。感谢@mekazu,这真是太美妙了!它对我很有效。 - KevinB
1
.toArray(Class[]::new)); - Mina

19

我发现,如果您将@Id字段命名为'Id',并且具有Id的公共getter,则它将在JSON中显示。 Id将显示为名为'id'的JSON键。

例如:@Id @Column(name="PERSON_ROLE_ID") private Long Id;

如果具有公共getter,则名称为“Id”的@EmbeddedId字段也适用,此时Id的字段将显示为JSON对象。

例如:@EmbeddedId private PrimaryKey Id;

令人惊讶的是,这是大小写敏感的,即使对于Java字段来说更常见的名称"id"也不起作用。

我必须说,我完全是偶然发现的,因此我不知道这是否是一种被接受的约定,或者是否适用于Spring Data和REST的先前或未来版本。因此,我已经包括了我的Maven pom的相关部分,以防它对版本敏感...

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.0.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-rest</artifactId>
    </dependency>
    <dependency>
        <groupId>com.oracle</groupId>
        <artifactId>ojdbc7</artifactId>
        <version>12.1.0.2</version>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
</dependencies>

不遵循Java命名规范的缺点是,诸如SpEL之类的bean评估语言将无法轻松地引用该字段。 - Mark
像魔法般完美地工作 +10 - r007
超级棒!这个id对我有效。请注意:在json中,它只显示为id:1,而且是在所有其他字段的末尾。 "addresses": [ { "custid": "1", "address1": "1, stree1", "address2": "stree1", "city": "city1", "zip": "1111", "ccc": "ccc1", "id": 1, "_links": { "self": { "href": "http://localhost:8080/addresses/1" }, "address": { "href": "http://localhost:8080/addresses/1" } } }, - MPV
这比设置RepositoryRestConfigurer来为您想要的每个类公开Ids要容易得多。 - maximum

17

目前,SDR没有提供这样的方法。SDR Jira跟踪器上的此问题解释了为什么这可能不可行(也许不应该可行)。

基本上,论点是因为ID已经包含在响应中的self链接中,所以你不需要将它们暴露为对象本身的属性。

话虽如此,您可以使用反射来检索所有具有javax.persistence.Id注释的类,然后调用RepositoryRestConfiguration#exposeIdsFor(Class<?>... domainTypes)。


这里有一个例子使用反射(在这种情况下是ClassPathScanning),网址为http://www.thomas-letsch.de/2015/spring-data-rest-hateoas/。 - pdorgambide
话虽如此,特别是在无头服务和混合使用情况下,需要ID。必须有一个唯一的键来指向对象,并在后端被解释为该唯一键检索映射的对象。最好直接使用ID本身。 - Cunning

8

针对@mekasu的更新答案。在2.4版本中RepositoryRestConfigurer接口进行了一些更改。

2.4之前:

import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import javax.persistence.EntityManager;
import javax.persistence.metamodel.Type;

@Configuration
public class Config implements RepositoryRestConfigurer {

    @Autowired
    private EntityManager entityManager;

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(entityManager.getMetamodel().getEntities().stream().map(Type::getJavaType).toArray(Class[]::new));
    }
}

文章 2.4

import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import javax.persistence.EntityManager;
import javax.persistence.metamodel.Type;

@Configuration
public class Config implements RepositoryRestConfigurer {

    @Autowired
    private EntityManager entityManager;

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
        config.exposeIdsFor(entityManager.getMetamodel().getEntities().stream().map(Type::getJavaType).toArray(Class[]::new));
    }
}

Type::getJavaType 究竟是哪个导入?请勿省略导入声明。 - user17408941
Type::getJavaType is javax.persistence.metamodel.Type - Jonas Pedersen

3

尝试这个配置。对我来说完全正常工作。

@Configuration
public class RestConfiguration extends RepositoryRestConfigurerAdapter{

      @PersistenceContext
      private EntityManager entityManager;

      @Override
      public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
           //TODO: Expose for specific entity!
           //config.exposeIdsFor(Officer.class);
           //config.exposeIdsFor(Position.class);

           //TODO: Expose id for all entities!
           entityManager.getMetamodel().getEntities().forEach(entity->{
                try {
                     System.out.println("Model: " + entity.getName());
                     Class<? extends Object> clazz = Class.forName(String.format("yourpackage.%s", entity.getName()));
                     config.exposeIdsFor(clazz);
                } catch (Exception e) {
                     System.out.println(e.getMessage());
                }
            });
    }
}

它完美地工作。然而,RepositoryRestConfigurerAdapter已被弃用。我扩展了RepositoryRestConfigurer类并覆盖了configureRepositoryRestConfiguration方法,并将您的代码按原样编写。现在它与我一起良好地运行。 - Vattic

3

这里有一个简短的解决方案,仅使用springframework中的东西来暴露所有id:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;

@Configuration
public class MyRepositoryRestConfigurer implements RepositoryRestConfigurer {
    @Autowired
    private Repositories repositories;

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
        this.repositories.iterator().forEachRemaining(r -> {
            config.exposeIdsFor(r);
        });
    }
}

2
您可以使用此方法来查找EntityManagerFactory的所有@Entity类:
private List<Class<?>> getAllManagedEntityTypes(EntityManagerFactory entityManagerFactory) {
    List<Class<?>> entityClasses = new ArrayList<>();
    Metamodel metamodel = entityManagerFactory.getMetamodel();
    for (ManagedType<?> managedType : metamodel.getManagedTypes()) {
        Class<?> javaType = managedType.getJavaType();
        if (javaType.isAnnotationPresent(Entity.class)) {
            entityClasses.add(managedType.getJavaType());
        }
    }
    return entityClasses;
}

然后,为了公开所有实体类的ID:
@Configuration
public class RestConfig extends RepositoryRestMvcConfiguration {

    @Bean
    public RepositoryRestConfigurer repositoryRestConfigurer(EntityManagerFactory entityManagerFactory) {
        List<Class<?>> entityClasses = getAllManagedEntityTypes(entityManagerFactory);

        return new RepositoryRestConfigurerAdapter() {

            @Override
            public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
                for (Class<?> entityClass : entityClasses) {
                    config.exposeIdsFor(entityClass);
                }
            }
    }
}

2

以下是基于@FrancoisGengler的答案的完整工作示例:

@SpringBootApplication
public class DataRestApplication {
    public static void main(String[] args) {
        SpringApplication.run(DataRestApplication.class, args);
    }

    @Bean
    public RepositoryRestConfigurer repositoryRestConfigurer(EntityManager entityManager) {
        return RepositoryRestConfigurer.withConfig(config -> {
            config.exposeIdsFor(entityManager.getMetamodel().getEntities()
                    .stream().map(Type::getJavaType).toArray(Class[]::new));
        });
    }
}

通过将 entityManager.getMetamodel().getEntities() .stream().map(Type::getJavaType).toArray(Class[]::new) 替换为 Question.class,帮助我显示特定实体的ID。 - Tobias Hochgürtel

1
我分享的解决方案基于其他答案。在我的情况下,配置了多个数据库,但是我不知道为什么,需要自动装配EntityManagerFactory实例。
@Db1 @Autowire
EntityManagerFactory entityManagerFactoryDb1;

@Db2 @Autowire
EntityManagerFactory entityManagerFactoryDb2;

现在我需要一种方法流式传输所有来自所有注入的持久单元收集到的实体类。(也许,可以检查@Entity注释或一个自定义注释,比如@EntityRestExposeId。)
    private void forEachEntityClass(final Consumer<? super Class<?>> consumer) {
        Arrays.stream(DataRestConfiguration.class.getDeclaredFields())
                .filter(f -> {
                    final int modifiers = f.getModifiers();
                    return !Modifier.isStatic(modifiers);
                })
                .filter(f -> EntityManagerFactory.class.isAssignableFrom(f.getType()))
                .map(f -> {
                    f.setAccessible(true);
                    try {
                        return (EntityManagerFactory) f.get(this);
                    } catch (final ReflectiveOperationException roe) {
                        throw new RuntimeException(roe);
                    }
                })
                .flatMap(emf -> emf.getMetamodel().getEntities().stream().map(EntityType::getJavaType))
                .forEach(consumer);
    }

调用exposeIdFor方法很简单。
@Configuration
class DataRestConfiguration {

    @Bean
    public RepositoryRestConfigurer repositoryRestConfigurer() {
        return RepositoryRestConfigurer.withConfig((configuration, registry) -> {
            forEachEntityClass(configuration::exposeIdsFor);
            // ...
        });
    }

    private void forEachEntityClass(final Consumer<? super Class<?>> consumer) {
        // ...
    }

    @Db1 @Autowired
    EntityManagerFactory entityManagerFactoryDb1;

    @Db2 @Autowired
    EntityManagerFactory entityManagerFactoryDb2;

    @Db3 @Autowired
    EntityManagerFactory entityManagerFactoryDb3;
}

1
以下代码看起来更漂亮:

.exposeIdsFor(entityManager.getMetamodel().getEntities().stream().map(entityType -> entityType.getJavaType()).toArray(Class[]::new))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接