如何通过 JPA 直接映射所有名称?

How to map ALL names directly by JPA?

给定类似邮政编码的分层 code/name 架构。

For example:

code = 101010

Code:

  • 100000 level 1 code (10....)
  • 101000 level 2 code (..10..)
  • 101010 level 3 code (....10)

Name (short name)

  • 100000 - A
  • 101000 - a
  • 101010 - i

Name (FullQualifiedName)

  • 100000 - A
  • 101000 - A->a
  • 101010 - A-a->i

编辑

我想要下面的代码(JPA 伪代码),但是不能。

@Entity
public class CodeName{
    // ....

    String code;   // 100101 levels = {100000, 100100, 100101}
    String name;   //  

    @HowToMapDirectedToNameOfCode('100000') // @SecondTable ?
    String name1;  

    @HowToMapDirectedToNameOfCode('100100')
    String name2; 

    @HowToMapDirectedToNameOfCode('100101')
    String name3; 

    String getFullQualifiedName(){
        return String.format("%s->%s->%s", name1, name2, name3);
    }

    // getter and setter
}

但在原生中相对容易SQL:

SELECT (select p1.name from codename p1 where p1.code= concat( substring(p.code,1,2), "0000") ) province,
(select p2.name from codename p2 where p2.code= concat( substring(p.code,1,4), "00") ) city,
(select p3.name from codename p3 where p3.code=p.code) area 

FROM codename p WHERE p.code = '100101';

因此,我将其实现为以下代码片段。

@Entity
public class CodeName{
    // ....

    String code;   // 100000,    101000, 100101
    String name;   // province,  city  , area 

    @Transient
    String name1;  // mapping directly?

    @Transient
    String name2;  // mapping directly?

    @Transient
    String name3;  // mapping directly?

    String getFullQualifiedName(){
        return String.format("%s->%s->%s", name1, name2, name3);
    }

    // getter and setter
}

public interface CodeNameRepository extends CrudRepository<CodeName, Long>, CodeNameRepositoryCustom  {

    @Query(" FROM CodeName p  " +
           " WHERE p.code = CONCAT(SUBSTRING(?1, 1, 2), '0000') " +
           " OR p.code = CONCAT(SUBSTRING(?1, 1, 4), '00') " +
           " OR p.code = ?1")
    List<CodeName> findAllLevelsByCode(String code);

}

@Component
public class CodeNameRepositoryImpl implements CodeNameRepositoryCustom {
    @Autowired
    private CodeNameRepository codeNameRepository ;

    @Override
    public CodeName CodeNamefindFullQualifiedNameByCode(String code) {
        List<CodeName> codeNames= codeNameRepository .findAllLevelsByCode(code);
        CodeName codeName;
        // extra name1, name2, name3 from list,
        // fill code, name, name1, name2, name3 to codeName and 
        return codeName;
    }
}

但是它有很多限制。

JPA可以直接映射所有的@Transient名称吗?

您可以按如下方式对您的代码存储库实体进行技术建模:

 public class CodeName {

   @Id
   @GeneratedValue(GenerationStrategy.AUTO)
   @Column
   private Long id;

   @ManyToOne
   private CodeName parent;

   @OneToMany(mappedBy = "parent")
   private List<CodeName> children;       

   @Column
   private String name;

   @Transient
   public String getFullyQualifiedName() {
     List<String> names = new ArrayList<>();         
     names.add(name);
     CodeName theParent = parent;
     while(theParent != null) {
       names.add(theParent.getName());
       theParent = theParent.parent;
     }
     Collections.reverse(names);
     return StringUtils.join(names, "->");
  }           
}

因为父关系会被提前获取,因为它们映射为 @ManyToOne,您基本上可以从任何子 CodeName 实体开始,向上遍历它与根的 parent/child 关系。这基本上允许 getFullyQualifiedName 方法在运行时为您构建名称。

如果执行此操作时出现性能问题,您始终可以按照您的描述通过添加 @Column private String fullyQualifiedName 提前在您的实体中对名称进行数据挖掘,并确保在创建代码时插入该字段。然后我添加到我的实体的瞬态方法可以被删除,因为你在数据插入时缓存名称。

可以编写一个 JPQL,它等同于您的 SQL 查询。唯一棘手的部分是将嵌套 select 重写为交叉连接,因为 JPA 不支持嵌套 select 并且您需要连接不相关的实体。另一方面,JPQL 以与 SQL 相同的方式支持函数 CONCATSUBSTRING。请参阅以下 JPQL 查询,它应该为您提供问题中 SQL 查询的结果:

SELECT p1.name // province
  , p2.name // city
  , p.name // area
FROM CodeName p, CodeName p1, CodeName p2
WHERE p.code = '100101'
AND p1.code = concat( substring(p.code,1,2), "0000")
AND p2.code= concat( substring(p.code,1,4), "00")

上述查询将在一行中为您提供 3 个值,这些值无法映射到单个实体中。因此,查询的结果将是 Object[] 数组的列表。您也可以将原始实体添加到 select 子句中:SELECT p1.name, p2.name, p.name, p FROM ...。这样,您可以稍后处理结果列表并将前三个值分配给实体的瞬态字段:

Object[] rows = query.getResultList();
for (Object row : rows) {
  CodeName c = (CodeName)row[3];
  c.setName1((String)row[0]);
  c.setName2((String)row[1]);
  c.setName3((String)row[2]);
}