春季数据JDBC。无法为枚举添加自定义转换器



我想将枚举作为我的实体的字段。

我的应用程序看起来像:

弹簧启动版本

plugins {
id 'org.springframework.boot' version '2.6.2' apply false

存储 库:

@Repository
public interface MyEntityRepository extends PagingAndSortingRepository<MyEntity, UUID> {
...

实体:

@Table("my_entity")
public class MyEntity{
...
private FileType fileType;
// get + set
}

枚举声明:

public enum FileType {
TYPE_1(1),
TYPE_2(2);
int databaseId;
public static FileType byDatabaseId(Integer databaseId){
return Arrays.stream(values()).findFirst().orElse(null);
}
FileType(int databaseId) {
this.databaseId = databaseId;
}
public int getDatabaseId() {
return databaseId;
}
}

我的尝试:

我找到了以下答案并尝试遵循它:https://stackoverflow.com/a/53296199/2674303

所以我添加了豆子

@Bean
public JdbcCustomConversions jdbcCustomConversions() {
return new JdbcCustomConversions(asList(new DatabaseIdToFileTypeConverter(), new FileTypeToDatabaseIdConverter()));
}

变换 器:

@WritingConverter
public class FileTypeToDatabaseIdConverter implements Converter<FileType, Integer> {
@Override
public Integer convert(FileType source) {
return source.getDatabaseId();
}
}
@ReadingConverter
public class DatabaseIdToFileTypeConverter implements Converter<Integer, FileType> {
@Override
public FileType convert(Integer databaseId) {
return FileType.byDatabaseId(databaseId);
}
}

但是我看到错误:

在类路径资源中定义的 bean 'jdbcCustomConversions' [org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfiguration$SpringBootJdbcConfiguration.class], 无法注册。具有该名称的豆子已经存在 在 my.pack.Main 中定义,覆盖被禁用。

我试图将方法jdbcCustomConversions()重命名为myJdbcCustomConversions()。它有助于避免上述错误,但在实体持久性期间未调用转换器,我看到另一个错误,即应用程序尝试保存字符串但数据库类型为 bigint。

20:39:10.689  DEBUG  [main] o.s.jdbc.core.StatementCreatorUtils: JDBC getParameterType call failed - using fallback method instead: org.postgresql.util.PSQLException: ERROR: column "file_type" is of type bigint but expression is of type character varying
Hint: You will need to rewrite or cast the expression.
Position: 174 

我还尝试使用最新(当前)版本的 spring boot:

id 'org.springframework.boot' version '2.6.2' apply false

但这并没有帮助。

我错过了什么? 如何将枚举正确映射到整数列?

附言

我使用以下代码进行测试:

@SpringBootApplication
@EnableJdbcAuditing
@EnableScheduling
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Main.class, args);
MyEntityRepositoryrepository = applicationContext.getBean(MyEntityRepository.class);
MyEntity entity =  new MyEntity();
...
entity.setFileType(FileType.TYPE_2);
repository.save(entity);
}
@Bean
public ModelMapper modelMapper() {
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT)
.setFieldMatchingEnabled(true)
.setSkipNullEnabled(true)
.setFieldAccessLevel(PRIVATE);
return mapper;
}

@Bean
public AbstractJdbcConfiguration jdbcConfiguration() {
return new MySpringBootJdbcConfiguration();
}
@Configuration
static class MySpringBootJdbcConfiguration extends AbstractJdbcConfiguration {
@Override
protected List<?> userConverters() {
return asList(new DatabaseIdToFileTypeConverter(), new FileTypeToDatabaseIdConverter());
}
}
}

更新

我的代码是:

@SpringBootApplication
@EnableJdbcAuditing
@EnableScheduling
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Main.class, args);
MyEntityRepositoryrepository = applicationContext.getBean(MyEntityRepository.class);
MyEntity entity =  new MyEntity();
...
entity.setFileType(FileType.TYPE_2);
repository.save(entity);
}
@Bean
public ModelMapper modelMapper() {
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT)
.setFieldMatchingEnabled(true)
.setSkipNullEnabled(true)
.setFieldAccessLevel(PRIVATE);
return mapper;
}
@Bean
public AbstractJdbcConfiguration jdbcConfiguration() {
return new MySpringBootJdbcConfiguration();
}
@Configuration
static class MySpringBootJdbcConfiguration extends AbstractJdbcConfiguration {
@Override
protected List<?> userConverters() {
return asList(new DatabaseIdToFileTypeConverter(), new FileTypeToDatabaseIdConverter());
}
@Bean
public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext,
NamedParameterJdbcOperations operations,
@Lazy RelationResolver relationResolver,
JdbcCustomConversions conversions,
Dialect dialect) {
JdbcArrayColumns arrayColumns = dialect instanceof JdbcDialect ? ((JdbcDialect) dialect).getArraySupport()
: JdbcArrayColumns.DefaultSupport.INSTANCE;
DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations(),
arrayColumns);
return new MyJdbcConverter(
mappingContext,
relationResolver,
conversions,
jdbcTypeFactory,
dialect.getIdentifierProcessing()
);
}
}
static class MyJdbcConverter extends BasicJdbcConverter {
MyJdbcConverter(
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
RelationResolver relationResolver,
CustomConversions conversions,
JdbcTypeFactory typeFactory,
IdentifierProcessing identifierProcessing) {
super(context, relationResolver, conversions, typeFactory, identifierProcessing);
}
@Override
public int getSqlType(RelationalPersistentProperty property) {
if (FileType.class.equals(property.getActualType())) {
return Types.BIGINT;
} else {
return super.getSqlType(property);
}
}
@Override
public Class<?> getColumnType(RelationalPersistentProperty property) {
if (FileType.class.equals(property.getActualType())) {
return Long.class;
} else {
return super.getColumnType(property);
}
}
}
}

但是我遇到错误:

Caused by: org.postgresql.util.PSQLException: Cannot convert an instance of java.lang.String to type long
at org.postgresql.jdbc.PgPreparedStatement.cannotCastException(PgPreparedStatement.java:925)
at org.postgresql.jdbc.PgPreparedStatement.castToLong(PgPreparedStatement.java:810)
at org.postgresql.jdbc.PgPreparedStatement.setObject(PgPreparedStatement.java:561)
at org.postgresql.jdbc.PgPreparedStatement.setObject(PgPreparedStatement.java:931)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.setObject(HikariProxyPreparedStatement.java)
at org.springframework.jdbc.core.StatementCreatorUtils.setValue(StatementCreatorUtils.java:414)
at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValueInternal(StatementCreatorUtils.java:231)
at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValue(StatementCreatorUtils.java:146)
at org.springframework.jdbc.core.PreparedStatementCreatorFactory$PreparedStatementCreatorImpl.setValues(PreparedStatementCreatorFactory.java:283)
at org.springframework.jdbc.core.PreparedStatementCreatorFactory$PreparedStatementCreatorImpl.createPreparedStatement(PreparedStatementCreatorFactory.java:241)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:649)
... 50 more
Caused by: java.lang.NumberFormatException: For input string: "TYPE_2"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.parseLong(Long.java:631)
at org.postgresql.jdbc.PgPreparedStatement.castToLong(PgPreparedStatement.java:792)
... 59 more

请尝试以下操作:

@Bean
public AbstractJdbcConfiguration jdbcConfiguration() {
return new MySpringBootJdbcConfiguration();
}
@Configuration
static class MySpringBootJdbcConfiguration extends AbstractJdbcConfiguration {
@Override
protected List<?> userConverters() {
return List.of(new DatabaseIdToFileTypeConverter(), new FileTypeToDatabaseIdConverter());
}
}

解释:

Spring 抱怨自动配置类中的JdbcCustomConversions已经定义(由您的 bean 定义),并且您没有启用 bean 覆盖。

JdbcRepositoriesAutoConfiguration已经更改了几次,在 Spring 2.6.2 中它有:

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(AbstractJdbcConfiguration.class)
static class SpringBootJdbcConfiguration extends AbstractJdbcConfiguration {
}

反过来,AbstractJdbcConfiguration有:

@Bean
public JdbcCustomConversions jdbcCustomConversions() {
try {
Dialect dialect = applicationContext.getBean(Dialect.class);
SimpleTypeHolder simpleTypeHolder = dialect.simpleTypes().isEmpty() ? JdbcSimpleTypes.HOLDER
: new SimpleTypeHolder(dialect.simpleTypes(), JdbcSimpleTypes.HOLDER);
return new JdbcCustomConversions(
CustomConversions.StoreConversions.of(simpleTypeHolder, storeConverters(dialect)), userConverters());
} catch (NoSuchBeanDefinitionException exception) {
LOG.warn("No dialect found. CustomConversions will be configured without dialect specific conversions.");
return new JdbcCustomConversions();
}
}

如您所见,JdbcCustomConversions在任何方面都不是有条件的,因此定义自己的会导致冲突。幸运的是,它提供了一个扩展点userConverters()可以覆盖该扩展点以提供您自己的转换器。

更新

如评论中所述:

  • FileType.byDatabaseId被破坏 - 它忽略其输入参数

  • 由于 db 中的列类型是 BIGINT,因此您的转换器必须从 Long 转换,而不是从 Integer 转换,这解决了读取查询

  • 对于写入,有一个未解决的错误 https://github.com/spring-projects/spring-data-jdbc/issues/629 有一个硬编码的假设,即枚举转换为字符串,并且仅检查枚举 -> 字符串转换器。 由于我们要转换为 Long,我们需要通过对其进行子类化并将子类化转换器注册为@Bean来对BasicJdbcConverter进行修改。

您需要覆盖两种方法

  • public int getSqlType(RelationalPersistentProperty property)
  • public Class<?> getColumnType(RelationalPersistentProperty property)

我对 Enum 类型和相应的列类型进行了硬编码,但您可能希望对此进行更花哨。

@Bean
public AbstractJdbcConfiguration jdbcConfiguration() {
return new MySpringBootJdbcConfiguration();
}
@Configuration
static class MySpringBootJdbcConfiguration extends AbstractJdbcConfiguration {
@Override
protected List<?> userConverters() {
return List.of(new DatabaseIdToFileTypeConverter(), new FileTypeToDatabaseIdConverter());
}
@Bean
public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext,
NamedParameterJdbcOperations operations,
@Lazy RelationResolver relationResolver,
JdbcCustomConversions conversions,
Dialect dialect) {
JdbcArrayColumns arrayColumns = dialect instanceof JdbcDialect ? ((JdbcDialect) dialect).getArraySupport()
: JdbcArrayColumns.DefaultSupport.INSTANCE;
DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations(),
arrayColumns);
return new MyJdbcConverter(
mappingContext,
relationResolver,
conversions,
jdbcTypeFactory,
dialect.getIdentifierProcessing()
);
}
}
static class MyJdbcConverter extends BasicJdbcConverter {
MyJdbcConverter(
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
RelationResolver relationResolver,
CustomConversions conversions,
JdbcTypeFactory typeFactory,
IdentifierProcessing identifierProcessing) {
super(context, relationResolver, conversions, typeFactory, identifierProcessing);
}
@Override
public int getSqlType(RelationalPersistentProperty property) {
if (FileType.class.equals(property.getActualType())) {
return Types.BIGINT;
} else {
return super.getSqlType(property);
}
}
@Override
public Class<?> getColumnType(RelationalPersistentProperty property) {
if (FileType.class.equals(property.getActualType())) {
return Long.class;
} else {
return super.getColumnType(property);
}
}
}

最新更新