可能需要显式配置和注册自定义编解码器或 PojoCodec 来处理此类型
A custom Codec or PojoCodec may need to be explicitly configured and registered to handle this type
UserProfileModel 有两个嵌入式模型。我在这里简明扼要地给出:
@Document(collection = "USER_PROFILE")
public class UserProfileModel extends BaseModel<UserModel> {
public static final String FIELD_USER_ID = "userId";
public static final String FIELD_BASIC_INFO = "basicInfo";
public static final String FIELD_CONTACT_INFO = "contactInfo";
private String userId;
private BasicInfo basicInfo;
private ContactInfo contactInfo;
}
public class BasicInfo {
private String gender;
private LocalDate birthDate;
private String[] langs;
private String religiousView;
private String politicalView;
}
public class ContactInfo {
private String[] mobilePhones;
private Address address;
private SocialLink[] socialLinks;
private String[] websites;
private String[] emails;
}
写入UserProfileModel的转换器class:
@Component
@WritingConverter
public class UserProfileModelConverter implements Converter<UserProfileModel, Document> {
@Override
public Document convert(UserProfileModel s) {
Document doc = new Document();
if (null != s.getUserId())
doc.put(UserProfileModel.FIELD_USER_ID, s.getUserId());
if (null != s.getBasicInfo())
doc.put(UserProfileModel.FIELD_BASIC_INFO, s.getBasicInfo());
if (null != s.getContactInfo())
doc.put(UserProfileModel.FIELD_CONTACT_INFO, s.getContactInfo());
}
}
正在尝试像这样保存 UserProfileModel 对象:
@Autowired
private UserProfileRepository repo;
UserProfileModel profileModel = generateUserProfileModel();
repo.save(profileModel);
异常:
org.bson.codecs.configuration.CodecConfigurationException: An exception occurred when encoding using the AutomaticPojoCodec.
Encoding a BasicInfo: 'BasicInfo(gender=Male, birthDate=2020-05-05, langs=[Lang 1, Lang 2], religiousView=Islam (Sunni), politicalView=N/A)' failed with the following exception:
Failed to encode 'BasicInfo'. Encoding 'langs' errored with: Can't find a codec for class [Ljava.lang.String;.
A custom Codec or PojoCodec may need to be explicitly configured and registered to handle this type.
at org.bson.codecs.pojo.AutomaticPojoCodec.encode(AutomaticPojoCodec.java:53)
at org.bson.codecs.EncoderContext.encodeWithChildContext(EncoderContext.java:91)
at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:185)
at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:199)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:141)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:45)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:63)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:29)
如果我不通过添加 Mongo 配置为 UserProfileModel
使用转换器,则不会出现此异常并且一切正常。
但出于某种原因,我正在尝试使用转换器 class。
那么是转换器有问题还是需要修改class?
已针对此问题提出了 Jira 工单 here。
Can't find a codec for class [Ljava.lang.String;
以及他们的回复
The Document class currently supports only List, not native Java
array, so just replace with:
List codes = Arrays.asList("1112233", "2223344");
因此,不支持字符串数组。在模型中使用 List<String>
而不是 String[]
。
解决这个问题的两种方法:
- 要么将
String[]
更改为 List<String>
,如前所述
- 编写您的自定义编解码器(如果您无法控制 class)
解决方法二如下:
- 创建自定义代码
StringArrayCodec
- 将其与现有编解码器一起注册
- Start/restart 使用 mongo-java 驱动程序的应用程序
以下是 Kotlin 代码,但也可以转换为 java。
- StringArrayCodec.kt
import java.util.*
import org.bson.BsonReader
import org.bson.BsonType
import org.bson.BsonWriter
import org.bson.codecs.Codec
import org.bson.codecs.DecoderContext
import org.bson.codecs.EncoderContext
class StringArrayCodec : Codec<Array<String>> {
/**
* Encode an instance of the type parameter `T` into a BSON value.
* @param writer the BSON writer to encode into
* @param value the value to encode
* @param encoderContext the encoder context
*/
override fun encode(writer: BsonWriter?, value: Array<String>?, encoderContext: EncoderContext?) {
writer?.writeStartArray()
val isNonNull = value != null
if (isNonNull) {
writer?.writeBoolean(isNonNull)
value?.size?.let { writer?.writeInt32(it) }
for (i in value!!) {
writeValue(writer, i, encoderContext)
}
} else {
writer?.writeBoolean(!isNonNull)
}
writer?.writeEndArray()
}
private fun writeValue(writer: BsonWriter?, s: String, encoderContext: EncoderContext?) {
if (s == null) {
writer?.writeNull()
} else {
writer?.writeString(s)
}
}
/**
* Returns the Class instance that this encodes. This is necessary because Java does not reify generic types.
*
* @return the Class instance that this encodes.
*/
override fun getEncoderClass(): Class<Array<String>>? {
return Array<String>::class.java
}
/**
* Decodes a BSON value from the given reader into an instance of the type parameter `T`.
*
* @param reader the BSON reader
* @param decoderContext the decoder context
* @return an instance of the type parameter `T`.
*/
override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): Array<String>? {
reader?.readStartArray()
val isNonNull = reader?.readBoolean()
val tempArray: Array<String?>?
if (isNonNull == true) {
val size = reader.readInt32()
tempArray = arrayOfNulls(size)
for (i in 0 until size) {
tempArray[i] = readValue(reader, decoderContext)
}
} else {
tempArray = null
}
val array: Array<String>? = if (isNonNull == true) {
Arrays.stream(tempArray)
.filter { s ->
s != null
}
.toArray() as Array<String>
} else {
null
}
reader?.readEndArray()
return array
}
private fun readValue(reader: BsonReader, decoderContext: DecoderContext?): String? {
val bsonType: BsonType = reader.currentBsonType
return if (bsonType == BsonType.NULL) {
reader.readNull()
null
} else {
reader.readString()
}
}
}
- 将自定义编解码器注册为
CodecRegistries.fromCodecs(StringArrayCodec())
MongodbConfig.kt
import com.mongodb.ConnectionString
import com.mongodb.MongoClientSettings
import com.mongodb.client.MongoClient
import com.mongodb.client.MongoClients
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import org.bson.Document
import org.bson.codecs.configuration.CodecRegistries
import org.bson.codecs.configuration.CodecRegistries.fromProviders
import org.bson.codecs.configuration.CodecRegistries.fromRegistries
import org.bson.codecs.configuration.CodecRegistry
import org.bson.codecs.pojo.PojoCodecProvider
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.stereotype.Component
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "mongodb")
@ConditionalOnProperty(name = ["mongodb.enable"], havingValue = "true", matchIfMissing = false)
@Component
class MongodbConfig
(
@Value("${mongodb.uri}")
val connectionUri: String,
@Value("${mongodb.database}")
val database: String,
@Value("${mongodb.collection-name}")
val collectionName: String
) {
@Bean
fun mongoClient(): MongoClient {
return MongoClients.create(mongodbSettings())
}
@Bean
fun mongoDatabase(mongoClient: MongoClient): MongoDatabase {
return mongoClient.getDatabase(database)
}
@Bean
fun mongodbCollection(mongoDatabase: MongoDatabase): MongoCollection<Document> {
return mongoDatabase.getCollection(collectionName)
}
fun mongodbSettings(): MongoClientSettings {
val pojoCodecRegistry: CodecRegistry = fromRegistries(
CodecRegistries.fromCodecs(StringArrayCodec()), // <---- this is the custom codec
MongoClientSettings.getDefaultCodecRegistry(),
fromProviders(PojoCodecProvider.builder().automatic(true).build())
)
val connectionString = ConnectionString(connectionUri)
return MongoClientSettings.builder()
.codecRegistry(pojoCodecRegistry)
.applyConnectionString(connectionString)
.build()
}
}
- 依赖和驱动版本
implementation("org.mongodb:mongodb-driver-sync:4.2.0") {
because("To connect mongodb instance")
}
UserProfileModel 有两个嵌入式模型。我在这里简明扼要地给出:
@Document(collection = "USER_PROFILE")
public class UserProfileModel extends BaseModel<UserModel> {
public static final String FIELD_USER_ID = "userId";
public static final String FIELD_BASIC_INFO = "basicInfo";
public static final String FIELD_CONTACT_INFO = "contactInfo";
private String userId;
private BasicInfo basicInfo;
private ContactInfo contactInfo;
}
public class BasicInfo {
private String gender;
private LocalDate birthDate;
private String[] langs;
private String religiousView;
private String politicalView;
}
public class ContactInfo {
private String[] mobilePhones;
private Address address;
private SocialLink[] socialLinks;
private String[] websites;
private String[] emails;
}
写入UserProfileModel的转换器class:
@Component
@WritingConverter
public class UserProfileModelConverter implements Converter<UserProfileModel, Document> {
@Override
public Document convert(UserProfileModel s) {
Document doc = new Document();
if (null != s.getUserId())
doc.put(UserProfileModel.FIELD_USER_ID, s.getUserId());
if (null != s.getBasicInfo())
doc.put(UserProfileModel.FIELD_BASIC_INFO, s.getBasicInfo());
if (null != s.getContactInfo())
doc.put(UserProfileModel.FIELD_CONTACT_INFO, s.getContactInfo());
}
}
正在尝试像这样保存 UserProfileModel 对象:
@Autowired
private UserProfileRepository repo;
UserProfileModel profileModel = generateUserProfileModel();
repo.save(profileModel);
异常:
org.bson.codecs.configuration.CodecConfigurationException: An exception occurred when encoding using the AutomaticPojoCodec.
Encoding a BasicInfo: 'BasicInfo(gender=Male, birthDate=2020-05-05, langs=[Lang 1, Lang 2], religiousView=Islam (Sunni), politicalView=N/A)' failed with the following exception:
Failed to encode 'BasicInfo'. Encoding 'langs' errored with: Can't find a codec for class [Ljava.lang.String;.
A custom Codec or PojoCodec may need to be explicitly configured and registered to handle this type.
at org.bson.codecs.pojo.AutomaticPojoCodec.encode(AutomaticPojoCodec.java:53)
at org.bson.codecs.EncoderContext.encodeWithChildContext(EncoderContext.java:91)
at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:185)
at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:199)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:141)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:45)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:63)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:29)
如果我不通过添加 Mongo 配置为 UserProfileModel
使用转换器,则不会出现此异常并且一切正常。
但出于某种原因,我正在尝试使用转换器 class。
那么是转换器有问题还是需要修改class?
已针对此问题提出了 Jira 工单 here。
Can't find a codec for class [Ljava.lang.String;
以及他们的回复
The Document class currently supports only List, not native Java array, so just replace with:
List codes = Arrays.asList("1112233", "2223344");
因此,不支持字符串数组。在模型中使用 List<String>
而不是 String[]
。
解决这个问题的两种方法:
- 要么将
String[]
更改为List<String>
,如前所述 - 编写您的自定义编解码器(如果您无法控制 class)
解决方法二如下:
- 创建自定义代码
StringArrayCodec
- 将其与现有编解码器一起注册
- Start/restart 使用 mongo-java 驱动程序的应用程序
以下是 Kotlin 代码,但也可以转换为 java。
- StringArrayCodec.kt
import java.util.*
import org.bson.BsonReader
import org.bson.BsonType
import org.bson.BsonWriter
import org.bson.codecs.Codec
import org.bson.codecs.DecoderContext
import org.bson.codecs.EncoderContext
class StringArrayCodec : Codec<Array<String>> {
/**
* Encode an instance of the type parameter `T` into a BSON value.
* @param writer the BSON writer to encode into
* @param value the value to encode
* @param encoderContext the encoder context
*/
override fun encode(writer: BsonWriter?, value: Array<String>?, encoderContext: EncoderContext?) {
writer?.writeStartArray()
val isNonNull = value != null
if (isNonNull) {
writer?.writeBoolean(isNonNull)
value?.size?.let { writer?.writeInt32(it) }
for (i in value!!) {
writeValue(writer, i, encoderContext)
}
} else {
writer?.writeBoolean(!isNonNull)
}
writer?.writeEndArray()
}
private fun writeValue(writer: BsonWriter?, s: String, encoderContext: EncoderContext?) {
if (s == null) {
writer?.writeNull()
} else {
writer?.writeString(s)
}
}
/**
* Returns the Class instance that this encodes. This is necessary because Java does not reify generic types.
*
* @return the Class instance that this encodes.
*/
override fun getEncoderClass(): Class<Array<String>>? {
return Array<String>::class.java
}
/**
* Decodes a BSON value from the given reader into an instance of the type parameter `T`.
*
* @param reader the BSON reader
* @param decoderContext the decoder context
* @return an instance of the type parameter `T`.
*/
override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): Array<String>? {
reader?.readStartArray()
val isNonNull = reader?.readBoolean()
val tempArray: Array<String?>?
if (isNonNull == true) {
val size = reader.readInt32()
tempArray = arrayOfNulls(size)
for (i in 0 until size) {
tempArray[i] = readValue(reader, decoderContext)
}
} else {
tempArray = null
}
val array: Array<String>? = if (isNonNull == true) {
Arrays.stream(tempArray)
.filter { s ->
s != null
}
.toArray() as Array<String>
} else {
null
}
reader?.readEndArray()
return array
}
private fun readValue(reader: BsonReader, decoderContext: DecoderContext?): String? {
val bsonType: BsonType = reader.currentBsonType
return if (bsonType == BsonType.NULL) {
reader.readNull()
null
} else {
reader.readString()
}
}
}
- 将自定义编解码器注册为
CodecRegistries.fromCodecs(StringArrayCodec())
MongodbConfig.kt
import com.mongodb.ConnectionString
import com.mongodb.MongoClientSettings
import com.mongodb.client.MongoClient
import com.mongodb.client.MongoClients
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import org.bson.Document
import org.bson.codecs.configuration.CodecRegistries
import org.bson.codecs.configuration.CodecRegistries.fromProviders
import org.bson.codecs.configuration.CodecRegistries.fromRegistries
import org.bson.codecs.configuration.CodecRegistry
import org.bson.codecs.pojo.PojoCodecProvider
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.stereotype.Component
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "mongodb")
@ConditionalOnProperty(name = ["mongodb.enable"], havingValue = "true", matchIfMissing = false)
@Component
class MongodbConfig
(
@Value("${mongodb.uri}")
val connectionUri: String,
@Value("${mongodb.database}")
val database: String,
@Value("${mongodb.collection-name}")
val collectionName: String
) {
@Bean
fun mongoClient(): MongoClient {
return MongoClients.create(mongodbSettings())
}
@Bean
fun mongoDatabase(mongoClient: MongoClient): MongoDatabase {
return mongoClient.getDatabase(database)
}
@Bean
fun mongodbCollection(mongoDatabase: MongoDatabase): MongoCollection<Document> {
return mongoDatabase.getCollection(collectionName)
}
fun mongodbSettings(): MongoClientSettings {
val pojoCodecRegistry: CodecRegistry = fromRegistries(
CodecRegistries.fromCodecs(StringArrayCodec()), // <---- this is the custom codec
MongoClientSettings.getDefaultCodecRegistry(),
fromProviders(PojoCodecProvider.builder().automatic(true).build())
)
val connectionString = ConnectionString(connectionUri)
return MongoClientSettings.builder()
.codecRegistry(pojoCodecRegistry)
.applyConnectionString(connectionString)
.build()
}
}
- 依赖和驱动版本
implementation("org.mongodb:mongodb-driver-sync:4.2.0") {
because("To connect mongodb instance")
}