Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >使用spring-data-mongodb资源持久化路径对象

使用spring-data-mongodb资源持久化路径对象
EN

Stack Overflow用户
提问于 2021-09-22 03:53:34
回答 1查看 285关注 0票数 0

在一个项目中,我使用spring-boot-starter-data-mongodb:2.5.3,因此使用spring-data-mongodb:3.2.3,并有一个简化的实体类如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Document
public class Task {
  @Id
  private final String id;
  private final Path taskDir;
  ...

  // constructor, getters, setters
}

使用默认的Spring MongoDB存储库,允许通过其id检索任务。

Mongo配置看起来是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Configuration
@EnableMongoRepositories(basePackages = {
    "path.to.repository"
}, mongoTemplateRef = MongoConfig.MONGO_TEMPLATE_REF)
@EnableConfigurationProperties(MongoSettings.class)
public class MongoConfig extends MongoConfigurationSupport {

  private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
  public static final String MONGO_TEMPLATE_REF = "mongoAlTemplate";

  private final MongoSettings mongoSettings;

  @Autowired
  public MongoConfig(final MongoSettings mongoSettings) {
    this.mongoSettings = mongoSettings;
  }

  @Bean(name = "ourMongo", destroyMethod = "close")
  public MongoClient ourMongoClient() {
    MongoCredential credential =
        MongoCredential.createCredential(mongoSettings.getUser(),
                                         mongoSettings.getDb(),
                                         mongoSettings.getPassword());
    MongoClientSettings clientSettings = MongoClientSettings.builder()
        .readPreference(ReadPreference.primary())
        // enable optimistic locking for @Version and eTag usage
        .writeConcern(WriteConcern.ACKNOWLEDGED)
        .credential(credential)
        .applyToSocketSettings(
            builder -> builder.connectTimeout(15, TimeUnit.SECONDS)
                .readTimeout(1, TimeUnit.MINUTES))
        .applyToConnectionPoolSettings(
            builder -> builder.maxConnectionIdleTime(10, TimeUnit.MINUTES)
                .minSize(5).maxSize(20))
//        .applyToClusterSettings(
//            builder -> builder.requiredClusterType(ClusterType.REPLICA_SET)
//                .hosts(Arrays.asList(new ServerAddress("host1", 27017),
//                                     new ServerAddress("host2", 27017)))
//                .build())
        .build();
    return MongoClients.create(clientSettings);
  }

  @Override
  @Nonnull
  protected String getDatabaseName() {
    return mongoSettings.getDb();
  }

  @Bean(name = MONGO_TEMPLATE_REF)
  public MongoTemplate ourMongoTemplate() throws Exception {
    return new MongoTemplate(ourMongoClient(), getDatabaseName());
  }
}

如果试图通过taskRepository.save(task)保存任务,StackOverflowError就会结束

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java.lang.StackOverflowError
    at java.lang.ThreadLocal.get(ThreadLocal.java:160)
    at java.util.concurrent.locks.ReentrantReadWriteLock$Sync.tryReleaseShared(ReentrantReadWriteLock.java:423)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.releaseShared(AbstractQueuedSynchronizer.java:1341)
    at java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock.unlock(ReentrantReadWriteLock.java:881)
    at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:239)
    at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:201)
    at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:87)
    at org.springframework.data.mapping.context.MappingContext.getRequiredPersistentEntity(MappingContext.java:73)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writePropertyInternal(MappingMongoConverter.java:740)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeProperties(MappingMongoConverter.java:657)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeInternal(MappingMongoConverter.java:633)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writePropertyInternal(MappingMongoConverter.java:746)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeProperties(MappingMongoConverter.java:657)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeInternal(MappingMongoConverter.java:633)
    ...

在用taskDirTask类中的path对象@Transient进行注释时,我能够持久化任务,因此问题似乎与Java/Spring/MongoDB无法直接处理Path对象有关。

我的下一个尝试是在MongoConfig类中配置一个自定义转换器,以便在PathString表示之间进行转换:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
protected void configureConverters(
  MongoCustomConversions.MongoConverterConfigurationAdapter converterConfigurationAdapter) {
  LOG.info("configuring converters");
  converterConfigurationAdapter.registerConverter(new Converter<Path, String>() {
    @Override
    public String convert(@Nonnull Path path) {
      return path.normalize().toAbsolutePath().toString();
    }
  });
  converterConfigurationAdapter.registerConverter(new Converter<String, Path>() {
    @Override
    public Path convert(@Nonnull String path) {
      return Paths.get(path);
    }
  });
}

尽管错误依然存在。然后,我定义了Task对象和DBObject之间的直接转换,如在这个指南中所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
protected void configureConverters(
  MongoCustomConversions.MongoConverterConfigurationAdapter converterConfigurationAdapter) {
  LOG.info("configuring converters");
  converterConfigurationAdapter.registerConverter(new Converter<Task, DBObject>() {
    @Override
    public DBObject convert(@Nonnull Task source) {
    DBObject dbObject = new BasicDBObject();
      if (source.getTaskDirectory() != null) {
        dbObject.put("taskDir", source.getTaskDirectory().normalize().toAbsolutePath().toString());
      }
      ...
      return dbObject;
    }
  });
}

我还得到了StackOverflowError作为回报。通过我添加的log语句,我看到Spring调用了configureConverters方法,因此应该已经注册了自定义转换器。

为什么我还能拿到StackOverflowError呢?如何告诉Spring在持久化和读取时将Path对象视为String对象,将String值再次转换为Path对象?

更新:

现在,我遵循了正式文件中给出的示例,并将转换器重构为自己的类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import org.bson.Document;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;

import javax.annotation.Nonnull;

@WritingConverter
public class TaskWriteConverter implements Converter<Task, Document> {

  @Override
  public Document convert(@Nonnull Task source) {
    Document document = new Document();
    document.put("_id", source.getId());
    if (source.getTaskDir() != null) {
      document.put("taskDir", source.getTaskDir().normalize().toAbsolutePath().toString());
    }
    return document;
  }
}

MongoConfig类中的配置现在如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  @Override
  protected void configureConverters(
      MongoCustomConversions.MongoConverterConfigurationAdapter adapter) {
    LOG.info("configuring converters");
    adapter.registerConverter(new TaskWriteConverter());
    adapter.registerConverter(new TaskReadConverter());
    adapter.registerConverter(new Converter<Path, String>() {
      @Override
      public String convert(@Nonnull Path path) {
        return path.normalize().toAbsolutePath().toString();
      }
    });
    adapter.registerConverter(new Converter<String, Path>() {
      @Override
      public Path convert(@Nonnull String path) {
        return Paths.get(path);
      }
    });
  }

在将org.springframework.data的日志记录级别更改为debug之后,我在日志中看到,这些转换器也被选中:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
2021-09-23 14:09:20.469 [INFO ] [           main] MongoConfig                              configuring converters 
2021-09-23 14:09:20.480 [DEBUG] [           main] CustomConversions                        Adding user defined converter from class com.acme.Task to class org.bson.Document as writing converter. 
2021-09-23 14:09:20.480 [DEBUG] [           main] CustomConversions                        Adding user defined converter from class org.bson.Document to class com.acme.Task as reading converter. 
2021-09-23 14:09:20.481 [DEBUG] [           main] CustomConversions                        Adding user defined converter from interface java.nio.file.Path to class java.lang.String as writing converter. 
2021-09-23 14:09:20.481 [DEBUG] [           main] CustomConversions                        Adding user defined converter from class java.lang.String to interface java.nio.file.Path as reading converter.

但是,我看到大多数转换器都是多次添加的,也就是说,在应用程序访问存储库上的Adding converter from class java.lang.Character to class java.lang.String as writing converter.方法之前,我实际上已经为save找到了4次日志。由于我的自定义转换器仅在所有这些转换器第三次出现在日志中时才被添加,因此我感觉它们在某种程度上被改写为上次“迭代”中的日志--不包括我的自定义转换器。

再现该问题的测试用例如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@ŚpringBootTest
@AutoConfigureMockMvc
@PropertySource("classpath:application-test.properties")
public class SomeIT {
  
  @Autowired
  private TaskRepository taskRepository;
  ...


  @Test
  public void testTaskPersistence() throws Exception {
    Task task = new Task("1234", Paths.get("/home/roman"));
    taskRepository.save(task);
  }

   ...
}

测试方法只用于调查当前的持久性问题,在正常情况下根本不应该存在,因为集成测试一个大文件的上传、它的预处理等等。但是,由于Spring无法存储包含Path对象的实体,这个集成测试失败了。

请注意,对于简单的实体,我在使用概述的设置来持久化它们方面没有问题,而且我也在被文档化的MongoDB中看到了它们。

我还没有时间深入研究为什么Spring在Path对象中会出现这样的问题,或者为什么我的自定义转换器在CustomConversions日志输出的最后一次迭代中突然消失了。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-09-24 05:28:30

事实证明,配置mongoTemplate的方式会“覆盖”任何指定的自定义转换器,因此Spring无法使用这些转换器并将Path转换为String,反之亦然。

在将MongoConfig更改为下面的一个之后,我终于能够使用我的自定义转换器,从而按预期持久化实体:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Configuration
@EnableMongoRepositories(basePackages = {
    "path.to.repository"
}, mongoTemplateRef = MongoConfig.MONGO_TEMPLATE_REF)
@EnableConfigurationProperties(MongoSettings.class)
public class MongoConfig extends MongoConfigurationSupport {

  private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
  public static final String MONGO_TEMPLATE_REF = "mongoAlTemplate";

  private final MongoSettings mongoSettings;

  @Autowired
  public MongoConfig(final MongoSettings mongoSettings) {
    this.mongoSettings = mongoSettings;
  }

  @Bean(name = "ourMongo", destroyMethod = "close")
  public MongoClient ourMongoClient() {
    MongoCredential credential =
        MongoCredential.createCredential(mongoSettings.getUser(),
                                         mongoSettings.getDb(),
                                         mongoSettings.getPassword());
    MongoClientSettings clientSettings = MongoClientSettings.builder()
        .readPreference(ReadPreference.primary())
        // enable optimistic locking for @Version and eTag usage
        .writeConcern(WriteConcern.ACKNOWLEDGED)
        .credential(credential)
        .applyToSocketSettings(
            builder -> builder.connectTimeout(15, TimeUnit.SECONDS)
                .readTimeout(1, TimeUnit.MINUTES))
        .applyToConnectionPoolSettings(
            builder -> builder.maxConnectionIdleTime(10, TimeUnit.MINUTES)
                .minSize(5).maxSize(20))
//        .applyToClusterSettings(
//            builder -> builder.requiredClusterType(ClusterType.REPLICA_SET)
//                .hosts(Arrays.asList(new ServerAddress("host1", 27017),
//                                     new ServerAddress("host2", 27017)))
//                .build())
        .build();
    LOG.info("Mongo client initialized. Connecting with user {} to DB {}",
             mongoSettings.getUser(), mongoSettings.getDb());
    return MongoClients.create(clientSettings);
  }

  @Override
  @Nonnull
  protected String getDatabaseName() {
    return mongoSettings.getDb();
  }

  @Bean
  public MongoDatabaseFactory ourMongoDBFactory() {
    return new SimpleMongoClientDatabaseFactory(ourMongoClient(), getDatabaseName());
  }

  @Bean(name = MONGO_TEMPLATE_REF)
  public MongoTemplate ourMongoTemplate() throws Exception {
    return new MongoTemplate(ourMongoDBFactory(), mappingMongoConverter());
  }

  @Bean
  public MappingMongoConverter mappingMongoConverter() throws Exception {
    DbRefResolver dbRefResolver = new DefaultDbRefResolver(ourMongoDBFactory());
    MongoCustomConversions customConversions = customConversions();
    MongoMappingContext context = mongoMappingContext(customConversions);
    MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, context);
    // this one is actually needed otherwise the StackOverflowError re-appears!
    converter.setCustomConversions(customConversions);
    return converter;
  }

  @Bean
  @Override
  @Nonnull
  public MongoCustomConversions customConversions() {
    return  new MongoCustomConversions(
        Arrays.asList(new PathWriteConverter(), new PathReadConverter())
    );
  }
}

因此,不直接将MongoClient和数据库名称传递给mongoTemplate,而是将保存上述值的MongoDatabaseFactory对象和MappingMongoConverter对象作为模板的输入传递。

不幸的是,有必要在customConversion方法中传递两次mappingMongoConverter()对象。如果不这样做,StackOverflowError就会重新出现。

对于给定的配置,现在可以将Path转换为String,将String转换为Path,因此目前不需要从TaskDocument的自定义转换,反之亦然。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69283685

复制
相关文章
MVC架构在Asp.net中的应用和实现
摘要:本文主要论述了MVC架构的原理、优缺点以及MVC所能为Web应用带来的好处。并以“成都市信息化资产管理系统”框架设计为例,详细介绍其在Asp.net环境下的具体实现。旨在帮助Web设计开发者更好的了解和掌握MVC,合理利用MVC构建优秀的Web应用。
莫问今朝
2019/02/25
3.7K0
使用MVC实现登录功能
                  using System.Data.SqlClient;
wfaceboss
2019/04/08
9880
在 ASP.NET Core 中安装 MVC
到目前为止,我们在本系列视频中使用的 ASP.NET Core 项目是使用“空”项目模板生成的。目前这个项目没有设置和安装 MVC。
角落的白板报
2019/05/15
1.5K0
ASP.NET Core MVC中如何使用Session实现身份验证
我们需要在用户登录以后记录当前登录用户的会话状态,ASP.NET Core 已经内置发布了一个关于会话的程序包(Microsoft.Extensions.DependencyInjection),里面提供了用于管理会话状态的中间件。
跟着阿笨一起玩NET
2021/02/02
3.9K0
ASP.NET Core MVC中如何使用Session实现身份验证
WebSocket在ASP.NET MVC4中的简单实现
WebSocket 规范的目标是在浏览器中实现和服务器端双向通信。双向通信可以拓展浏览器上的应用类型,例如实时的数据推送、游戏、聊天等。有了WebSocket,我们就可以通过持久的浏览器和服务器的连接实现实时的数据通信,再也不用傻傻地使用连绵不绝的请求和常轮询的机制了,费时费力,当然WebSocket也不是完美的,当然,WebSocket还需要浏览器的支持,目前IE的版本必须在10以上才支持WebSocket,Chrome Safari的最新版本当然也都支持。本节简单介绍一个在服务器端和浏览器端实现WebS
小白哥哥
2018/03/07
2.6K0
WebSocket在ASP.NET MVC4中的简单实现
在ASP.NET MVC 4中使用Kendo UI Grid
Kendo UI 是Telerik推出的一套based on jQuery 的 Framework,提供了很多控件(Menu 、Grid 、Combox等...), 底层以Html5 + jQuery 来打造,并且兼容于各大浏览器,包含IE7、IE8。相关介绍可以参考AJAX式数据清单的新选择-Kendo UI Grid。 以下内容参考台湾的黑老大的文章:在ASP.NET MVC 4中使用Kendo UI Grid 建立一个ASP.NET MVC 4专案 使用NuGet安装KendoUIWeb及Kendo
张善友
2018/01/22
3.3K0
在ASP.NET MVC 4中使用Kendo UI Grid
ASP.NET MVC 4中的单页面应用程序
ASP.NET MVC 4 beta中包含了一个实验项目,用作开发“单页面应用程序(single page applications)”。该项目也称为ASP.NET SPA,其项目类型基于一组开源库以及WPF、Silverlight上流行的MVVM模式。 浏览器端 位于浏览器端技术组底部的是著名的jQuery库,与之一起的还有Unobtrusive Ajax、jQuery UI和jQuery Validation插件。 接下来的技术是Upshot。它是构建于jQuery和Knockout之上的数据访问和缓存
张善友
2018/01/19
1.6K0
在 Tekton 中如何实现审批功能
常见的 CICD 引擎并不适合直接提供给业务方使用。主要原因在于用户学习成本高、缺乏必要的鉴权、维护升级难度大。
陈少文
2021/06/26
2K0
在 Tekton 中如何实现审批功能
c# asp.net 实现分页(pager)功能
分页PagerHelper辅助类 using System; using System.Web; public class PagerHelper { #region 获取分页的Html代码 /// <summary> /// 获取分页的Html代码 /// 当前页码方法内部根据Request["page"]获取 /// </summary> /// <param name="pageSize">每一页数量</param> /// <param nam
纯粹是糖
2018/03/14
2.4K0
c#  asp.net 实现分页(pager)功能
JQuery文件上传插件ajaxFileUpload在Asp.net MVC中的使用
0 ajaxFileUpload简介 ajaxFileUpload插件是一个非常简单的基于Jquery的异步上传文件的插件,使用过程中发现很多与这个同名的,基于原始版本基础之上修改过的插件,文件版本比较多,我把我自己使用的ajaxFileUpload文件上传到博客园上了,想要使用的朋友可以下载:http://files.cnblogs.com/files/fonour/ajaxfileupload.js。 整个插件源码不到200行,实现非常简单,大致原理就是通过js动态创建隐藏的表单,然后进行提交操作,达到
阿炬
2018/05/11
3.3K0
在ASP.NET MVC中如何应用多个相同类型的ValidationAttribute?
ASP.NET MVC采用System.ComponentModel.DataAnnotations提供的元数据验证机制对Model实施验证,我们可以在Model类型或者字段/属性上应用相应的ValidationAttribute。但是在默认情况下,对于同一个类型的ValidationAttribute特性只允许一个应用到目标元素上——即使我们将AllowMultiple属性设置为True。这篇文章的目的就是为了解决这个问题。[源代码从这里下载] 一、一个自定义ValidationAttribute:Ran
蒋金楠
2018/02/07
2.1K0
在ASP.NET MVC中如何应用多个相同类型的ValidationAttribute?
在ASP.NET MVC中使用“RadioButtonList”和“CheckBoxList”
在《为HtmlHelper添加一个RadioButtonList扩展方法》中我通过对HtmlHelper和HtmlHelper<Model>的扩展使我们可以采用”RadioButtonList”的方式对一组类型为“radio”的<input>元素进行操作。昨天对对此进行了一些改进,并将“CheckBoxList”的功能添加进来。[源代码从这里下载] 一、有何特别之处? 和我的很多文章一样,旨在提供一种大体的解决方案,本解决方案旨在解决如下一些问题: 通过独立的组件对绑定到ListControl(ASP.NE
蒋金楠
2018/01/16
1.3K0
在ASP.NET MVC中使用“RadioButtonList”和“CheckBoxList”
《从零开始学ASP.NET CORE MVC》:VS2019创建ASP.NET Core Web程序(三)
步骤1:在Visual Studio 2019中创建新的asp.net Core项目
角落的白板报
2019/05/05
3.9K0
《从零开始学ASP.NET CORE MVC》:VS2019创建ASP.NET Core Web程序(三)
《从零开始学ASP.NET CORE MVC》:VS2017创建ASP.NET Core Web程序(三)
步骤1:在Visual Studio 2017中创建新的asp.net Core项目
角落的白板报
2019/05/05
2.8K0
《从零开始学ASP.NET CORE MVC》:VS2017创建ASP.NET Core Web程序(三)
在ASP.NET MVC 中获取当前URL、controller、action
一、URL的获取很简单,ASP.NET通用: 【1】获取 完整url (协议名+域名+虚拟目录名+文件名+参数)  string url=Request.Url.ToString();  【2】获取 虚拟目录名+页面名+参数:  string url=Request.RawUrl; (或 string url=Request.Url.PathAndQuery;) 【3】获取 虚拟目录名+页面名: string url=HttpContext.Current.Request.Url.AbsoluteP
欢醉
2018/01/22
2.4K0
ASP.NET Core MVC 概述
ASP.NET Core MVC 是使用“模型-视图-控制器”设计模式构建 Web 应用和 API 的丰富框架。 什么是 MVC 模式? 模型-视图-控制器 (MVC) 体系结构模式将应用程序分成 3
程序你好
2018/07/20
6.4K0
js checkbox复选框实现单选功能[通俗易懂]
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/139018.html原文链接:https://javaforall.cn
全栈程序员站长
2022/08/23
5.8K0
ASP.NET MVC 异步实现
在 MVC 中实现异步操作有两种方法,一种是使用jQuery的异步函数,另一种就是使用MVC的 AjaxHelper
李郑
2019/12/04
1.2K0
国内 Mono 相关文章汇总
一则新闻《软件服务提供商Xamarin融资1200万美元》,更详细的内容可以看Xamarin的官方博客Xamarin raises $12M to help you make better apps faster →。这篇新闻里告诉了我们目前Mono的用户规模“使用Xamarin软件的应用开发者已经超过15万,其中付费用户约为7500名。在Xamarin的客户中,还包括一些知名的企业,如美国国家仪器(National Instruments)和数字音乐订阅服务商Rdio等”。一直关注和研究Mono项目,今天
张善友
2018/01/26
11.4K0
在 ASP.NET MVC 中使用异步控制器
可以通过 AsyncController 类编写异步操作方法。 可以对长时间运行的、非 CPU 绑定的请求使用异步操作方法。 这样可避免在处理请求时阻塞 Web 服务器执行工作。 AsyncController 类通常用于长时间运行的 Web 服务调用。 本主题包含以下各节: 线程池处理请求的方式 处理异步请求 选择同步操作方法或异步操作方法 将同步操作方法转换为异步操作方法 并行执行多个操作 将特性添加到异步操作方法 使用 BeginMethod/EndMethod 模式 类参考 与本主题对应的包含源代码
逸鹏
2018/04/10
1.9K0

相似问题

在asp.net mvc中实现“预览”功能

37

在ASP.NET MVC中实现“记住我”功能

11

ASP.NET MVC C#如何使用此功能

11

如何在ASP.NET MVC中实现排序功能

13

在Asp.Net MVC中使用Google Workbox实现离线功能

227
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文