如何将带Long类型ID的自定义JPA模型与PicketLink正确映射?
这个问题的核心是PicketLink默认使用String类型作为身份分区(Partition)的ID,但你的JPA实体基类用了Long类型的主键,当PicketLink尝试将Realm的String ID赋值给RealmEntity的Long类型sid字段时,就会抛出类型不匹配的异常。下面提供两种可行的解决方案:
方案一:让PicketLink Partition类使用Long类型ID(推荐,无类型转换)
这种方案直接让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

