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

如何将带Long类型ID的自定义JPA模型与PicketLink正确映射?

解决PicketLink与Long类型JPA主键的映射问题

这个问题的核心是PicketLink默认使用String类型作为身份分区(Partition)的ID,但你的JPA实体基类用了Long类型的主键,当PicketLink尝试将Realm的String ID赋值给RealmEntity的Long类型sid字段时,就会抛出类型不匹配的异常。下面提供两种可行的解决方案:

这种方案直接让PicketLink的Realm类使用Long类型ID,和你的JPA实体主键类型保持一致,避免类型转换的麻烦。

步骤1:修改Realm类,实现Partition接口(不继承AbstractPartition)

由于AbstractPartition的ID是String类型,我们直接实现Partition接口,使用Long作为ID类型:

public class Realm implements Partition {
    private Long id;
    private String name;
    private String typeName;

    @AttributeProperty
    private boolean enforceSSL;
    @AttributeProperty
    private int numberFailedLoginAttempts;

    public Realm() {
    }

    public Realm(String name) {
        this.name = name;
        this.typeName = Realm.class.getName(); // 对应RealmEntity的typeName字段
    }

    @Override
    public Serializable getPartitionId() {
        return this.id;
    }

    public void setPartitionId(Serializable id) {
        this.id = (Long) id;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public String getType() {
        return this.typeName;
    }

    // 其他getter和setter方法
    public boolean isEnforceSSL() {
        return enforceSSL;
    }

    public void setEnforceSSL(boolean enforceSSL) {
        this.enforceSSL = enforceSSL;
    }

    public int getNumberFailedLoginAttempts() {
        return numberFailedLoginAttempts;
    }

    public void setNumberFailedLoginAttempts(int numberFailedLoginAttempts) {
        this.numberFailedLoginAttempts = numberFailedLoginAttempts;
    }
}

步骤2:配置JPA实体的主键生成策略

在你的AbstractEntity中,为sid字段添加主键生成策略(比如数据库自增),让JPA负责生成Long类型的ID:

@MappedSuperclass
public abstract class AbstractEntity implements Serializable, Cloneable {
    @Id
    @Identifier // PicketLink标记ID字段
    @Column(name = "SID")
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 数据库自增策略,根据你的数据库调整
    private Long sid;
    @Column(name = "INSERT_TIME")
    private Date insertTime;
    @Column(name = "UPDATE_TIME")
    private Date updateTime;

    // getters and setters
}

步骤3:确保PartitionManager配置正确

不需要额外修改配置,保持原有的配置即可,因为PicketLink会自动匹配Serializable类型的ID(Long是Serializable的子类):

builder
    .named("default.config")
    .stores()
    .jpa()
    .supportType(User.class, Role.class, Application.class, Realm.class)
    .supportGlobalRelationship(Grant.class, ApplicationAccess.class)
    .mappedEntity(App.class, AppUserRole.class, AppRole.class, AppUser.class, UserEntity.class, RelationshipIdentityTypeEntity.class, RealmEntity.class)
    .addContextInitializer((context, store) -> {
        if (store instanceof JPAIdentityStore) {
            if (!context.isParameterSet(JPAIdentityStore.INVOCATION_CTX_ENTITY_MANAGER)) {
                context.setParameter(JPAIdentityStore.INVOCATION_CTX_ENTITY_MANAGER, entityManager);
            }
        }
    });

方案二:使用自定义映射器处理String与Long的类型转换

如果你不想修改Realm类的继承结构(继续使用AbstractPartition的String ID),可以通过自定义映射器在PicketLink对象和JPA实体之间做类型转换。

步骤1:创建自定义Partition映射器

实现BasicPartitionMapper,处理ID的类型转换:

import org.picketlink.idm.jpa.mapper.BasicPartitionMapper;
import org.picketlink.idm.spi.IdentityStoreContext;

public class RealmMapper extends BasicPartitionMapper<Realm, RealmEntity> {

    @Override
    public void map(Realm partition, RealmEntity entity, IdentityStoreContext context) {
        super.map(partition, entity, context);
        // 将Realm的String ID转换为Long,赋值给RealmEntity的sid
        if (partition.getPartitionId() instanceof String) {
            entity.setSid(Long.parseLong((String) partition.getPartitionId()));
        }
    }

    @Override
    public void map(RealmEntity entity, Realm partition, IdentityStoreContext context) {
        super.map(entity, partition, context);
        // 将RealmEntity的Long sid转换为String,赋值给Realm的ID
        if (entity.getSid() != null) {
            partition.setId(entity.getSid().toString());
        }
    }
}

步骤2:在RealmEntity上指定映射器

修改RealmEntity的@IdentityManaged注解,添加自定义映射器:

@Entity
@IdentityManaged(value = Realm.class, mapper = RealmMapper.class)
public class RealmEntity extends AbstractEntity {
    @AttributeValue
    private String name;
    @PartitionClass
    private String typeName;
    @ConfigurationName
    private String configurationName;
    @AttributeValue
    private boolean enforceSSL;
    @AttributeValue
    private int numberFailedLoginAttempts;

    // getters and setters
}

步骤3:配置ID生成策略(可选)

如果你的JPA实体使用自增主键,同样需要在AbstractEntity的sid字段添加@GeneratedValue注解,确保ID由数据库生成。

测试验证

完成上述修改后,重新运行创建Realm的代码:

this.shsRealm = new Realm(REALM_SHS_NAME);
this.shsRealm.setEnforceSSL(true);
this.shsRealm.setNumberFailedLoginAttempts(3);
this.partitionManager.add(this.shsRealm);

此时PicketLink会正确处理ID类型,不会再抛出类型不匹配的异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:47:21