You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hibernate 6中AbstractSingleColumnStandardBasicType的使用及单列映射方案

Hibernate 6+ 实现List字段单列映射方案

针对你想把List字段存入Account单表列、不想用@ElementCollection的需求,Hibernate 6+ 依然支持自定义单列映射,只是原来依赖的AbstractTypeDescriptor API被调整了,下面给你两种可行方案:

方案1:用JPA @Converter快速实现(推荐)

这种方式最简单,不需要接触Hibernate底层API,完全匹配你不需要基于该字段查询、仅整体操作的场景。

步骤1:编写转换类

把List序列化为JSON字符串存到数据库,查询时再反序列化为List:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.List;

@Converter(autoApply = false)
public class StringListConverter implements AttributeConverter<List<String>, String> {

    private final ObjectMapper objectMapper = new ObjectMapper();

    // List转JSON字符串存库
    @Override
    public String convertToDatabaseColumn(List<String> attribute) {
        if (attribute == null || attribute.isEmpty()) {
            return null;
        }
        try {
            return objectMapper.writeValueAsString(attribute);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("List转JSON失败", e);
        }
    }

    // JSON字符串转List回实体
    @Override
    public List<String> convertToEntityAttribute(String dbData) {
        if (dbData == null || dbData.isEmpty()) {
            return List.of();
        }
        try {
            return objectMapper.readValue(dbData, objectMapper.getTypeFactory().constructCollectionType(List.class, String.class));
        } catch (JsonProcessingException e) {
            throw new RuntimeException("JSON转List失败", e);
        }
    }
}

步骤2:在实体类中配置字段

给Account实体的List字段加上@Convert注解,指定用上面的转换器:

import jakarta.persistence.*;
import java.util.List;

@Entity
@Table(name = "account")
public class Account {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    @Convert(converter = StringListConverter.class)
    @Column(columnDefinition = "TEXT") // 用TEXT存储更长的JSON内容
    private List<String> tags;

    // 省略getter、setter方法
}

方案2:自定义Hibernate BasicType(替代AbstractSingleColumnStandardBasicType)

如果一定要用Hibernate的自定义类型体系,6+版本里AbstractSingleColumnStandardBasicType还能使用,但TypeDescriptor相关API换成了JavaType和JdbcType,实现方式如下:

步骤1:自定义BasicType

继承AbstractSingleColumnBasicType,实现List与数据库字符串的转换逻辑:

import org.hibernate.type.AbstractSingleColumnBasicType;
import org.hibernate.type.descriptor.java.StringJavaType;
import org.hibernate.type.descriptor.jdbc.VarcharJdbcType;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;

public class ListStringType extends AbstractSingleColumnBasicType<List<String>> {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    public ListStringType() {
        // 指定JDBC类型为VARCHAR,自定义Java类型的转换逻辑
        super(VarcharJdbcType.INSTANCE, new StringJavaType() {
            @Override
            public List<String> fromString(CharSequence string) {
                if (string == null || string.length() == 0) {
                    return List.of();
                }
                try {
                    return objectMapper.readValue(string.toString(), objectMapper.getTypeFactory().constructCollectionType(List.class, String.class));
                } catch (JsonProcessingException e) {
                    throw new RuntimeException("JSON转List失败", e);
                }
            }

            @Override
            public String toString(List<String> value) {
                if (value == null || value.isEmpty()) {
                    return null;
                }
                try {
                    return objectMapper.writeValueAsString(value);
                } catch (JsonProcessingException e) {
                    throw new RuntimeException("List转JSON失败", e);
                }
            }
        });
    }

    @Override
    public String getName() {
        return "list-string"; // 自定义类型名称
    }
}

步骤2:注册并使用自定义类型

可以在实体类局部注册,也可以全局注册:

方式1:实体类局部注册

import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import jakarta.persistence.*;
import java.util.List;

@Entity
@Table(name = "account")
@TypeDef(name = "list-string", typeClass = ListStringType.class) // 注册自定义类型
public class Account {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    @Type(type = "list-string") // 使用自定义类型
    @Column(columnDefinition = "TEXT")
    private List<String> tags;

    // 省略getter、setter方法
}

方式2:全局注册(Spring Boot)

编写配置类将自定义类型注册到Hibernate:

import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.TypeContributor;
import org.hibernate.service.ServiceRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HibernateTypeConfig {

    @Bean
    public TypeContributor listStringTypeContributor() {
        return new TypeContributor() {
            @Override
            public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
                typeContributions.contributeType(new ListStringType());
            }
        };
    }
}

关于@ElementCollection的说明

正如你顾虑的,@ElementCollection会生成额外的关联表(比如account_tags),每次操作List都要处理关联表的增删改,对于你只需要整体操作、不需要单独访问元素的场景,确实没必要用,反而增加复杂度,优先选择上面两种单列存储方案。

内容的提问来源于stack exchange,提问作者tmaksimenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 15:07:20