使用 Spring Data Rest 时公开所有 ID
Posted
技术标签:
【中文标题】使用 Spring Data Rest 时公开所有 ID【英文标题】:Expose all IDs when using Spring Data Rest 【发布时间】:2015-09-03 22:37:00 【问题描述】:我想使用 Spring Rest 接口公开所有 ID。
我知道默认情况下,这样的 ID 不会通过其余接口公开:
@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 而无需在此 configureRepositoryRestConfiguration
方法中手动维护列表?
【问题讨论】:
查看this,了解一些有用的示例,了解如何公开所有实体或仅扩展或实现特定超类的标识符或接口,或标有一些特定注释。 【参考方案1】:如果您想公开所有实体类的 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]));
【讨论】:
.map(EntityType::getJavaType) 这应该是公认的答案,因为手动配置只能在最小的用例中进行。谢谢@mekazu,这是一件美丽的事情!它对我有用。 .toArray(Class[]::new));【参考方案2】:我发现,如果您将 @Id
字段命名为“Id
”,并且您有 Id
的公共 getter,它将显示在 JSON 中。 Id 将显示为名为“id
”的 JSON 键
例如:@Id @Column(name="PERSON_ROLE_ID") private Long Id;
这也适用于名为“Id
”的 @EmbeddedId
字段,只要它具有公共 getter。在这种情况下,Id 的字段将显示为 JSON 对象。
例如:@EmbeddedId private PrimaryKey Id;
令人惊讶的是,这是区分大小写的,调用 id 'id
' 不起作用,即使它是 Java 字段的更传统名称。
我应该说我完全是偶然发现了这一点,所以我不知道这是否是公认的约定,或者是否适用于 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 评估语言不容易引用该字段。 像魅力一样工作+10 超级发现! ID 为我工作。注意:在 json 中,它仅显示为 id: 1 ,它也在所有其他字段的末尾。 “地址”:[ “custid”:“1”,“address1”:“1,stree1”,“address2”:“stree1”,“city”:“city1”,“zip”:“1111”,“ccc ": "ccc1", "id": 1, "_links": "self": "href": "localhost:8080/addresses/1" , "address": "href": "localhost:8080/addresses/1" , 这比设置 RepositoryRestConfigurer 为您想要的每个类公开 Id 容易得多。【参考方案3】:目前,SDR 无法提供此功能。 SDR Jira 跟踪器上的This issue 解释了为什么这不可能(也许不应该)。
这个论点基本上是因为 ID 已经包含在响应中的 self
链接中,您不需要将它们作为对象本身的属性公开。
也就是说,您可以使用反射来检索具有javax.persistence.Id
注释的所有类,然后调用RepositoryRestConfiguration#exposeIdsFor(Class<?>... domainTypes)
。
【讨论】:
thomas-letsch.de/2015/spring-data-rest-hateoas有一个使用反射(在本例中为 ClassPathScannning)的示例 话虽如此,在无头服务和混合搭配用例上尤其需要 ID。必须有一个唯一键来指向对象,并在后端被解释为检索该唯一键的映射对象。还不如使用 ID 本身。【参考方案4】:@mekasu 的更新答案。 RepositoryRestConfigurer
接口在 2.4 中有所改变。
2.4 之前的版本:
@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
@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));
【讨论】:
【参考方案5】:试试这个配置。它对我来说非常好用。
@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 方法并按原样编写代码。它对我很好【参考方案6】:您可以使用此方法查找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);
【讨论】:
【参考方案7】:以下代码看起来更漂亮:
.exposeIdsFor(entityManager.getMetamodel().getEntities().stream().map(entityType -> entityType.getJavaType()).toArray(Class[]::new))
【讨论】:
【参考方案8】:基于Francois Gengler 答案的完整工作示例(请支持他的回答,但不要支持我的回答):
@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));
);
【讨论】:
【参考方案9】:也许您可以尝试将所有 id 字段包含在内。我还没有尝试过,但会继续发布。
public class ExposeAllRepositoryRestConfiguration extends RepositoryRestConfiguration
@Override
public boolean isIdExposedFor(Class<?> domainType)
return true;
Excerpt from this link
【讨论】:
【参考方案10】:您可以通过 exposeIdsFor 添加所有实体类。将“db.entity”替换为您放置实体的 whick 包。
@Configuration
public class CustomRepositoryRestConfigurer extends RepositoryRestConfigurerAdapter
Logger logger = Logger.getLogger(this.getClass());
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config)
Set<String> classNameSet = ClassTool.getClassName("db.entity", false);
for (String className : classNameSet)
try
config.exposeIdsFor(Class.forName(className));
catch (ClassNotFoundException e)
e.printStackTrace();
logger.info("exposeIdsFor : " + classNameSet);
ClassTool 是我的自定义函数,用于从给定包中获取类,您可以自己编写。
【讨论】:
【参考方案11】:这对我来说非常有效 (source here):
@Configuration
public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter
@Override
public void configureRepositoryRestConfiguration(final RepositoryRestConfiguration config)
final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
false);
provider.addIncludeFilter(new AnnotationTypeFilter(Entity.class));
final Set<BeanDefinition> beans = provider.findCandidateComponents("com.your.domain");
for (final BeanDefinition bean : beans)
try
config.exposeIdsFor(Class.forName(bean.getBeanClassName()));
catch (final ClassNotFoundException e)
// Can't throw ClassNotFoundException due to the method signature. Need to cast it
throw new IllegalStateException("Failed to expose `id` field due to", e);
它会找到所有带有 @Entity 注释的 bean 并公开它们。
【讨论】:
【参考方案12】:请为此找到一个简单的解决方案,避免查找相关实体。
@Component
public class EntityExposingIdConfiguration extends RepositoryRestConfigurerAdapter
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config)
try
Field exposeIdsFor = RepositoryRestConfiguration.class.getDeclaredField("exposeIdsFor");
exposeIdsFor.setAccessible(true);
ReflectionUtils.setField(exposeIdsFor, config, new ListAlwaysContains());
catch (NoSuchFieldException e)
e.printStackTrace();
class ListAlwaysContains extends ArrayList
@Override
public boolean contains(Object o)
return true;
【讨论】:
产量:The type RepositoryRestConfigurerAdapter is deprecated
【参考方案13】:
您可以尝试以下解决方案: - 首先将reflections 库导入您的POM 文件:
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
- 然后将您的 RepositoryConfig 类更改为:
@Configuration
public class RepositoryConfig extends RepositoryRestMvcConfiguration
@Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config)
Reflections reflections = new Reflections("com.example.entity");
Set<Class<?>> idExposedClasses = reflections.getTypesAnnotatedWith(Entity.class, false);
idExposedClasses.forEach(config::exposeIdsFor);
return config;
将 "com.example.entity" 更改为您的 Entity 包,一切顺利。祝你好运!
【讨论】:
【参考方案14】:我正在分享我基于 other answer 的解决方案。
在我配置多个数据库的情况下,我不知道为什么,但是我需要自动装配 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;
【讨论】:
以上是关于使用 Spring Data Rest 时公开所有 ID的主要内容,如果未能解决你的问题,请参考以下文章
如何禁用 Spring Data REST 存储库的默认公开?
默认情况下,不要使用Spring Data Rest和Jpa公开Entity类中的字段
Spring Data Rest:为扩展 Revision Repository 的 Repository 公开新端点