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

时间:2015-12-14 14:57:07

标签: jpa spring-data-jpa

给定类似邮政编码的分层代码/名称架构。

  

例如:

     

代码= 101010

     

代码:

     
      
  • 100000第1级代码(10 ....)
  •   
  • 101000二级代码(..10 ..)
  •   
  • 101010 3级代码(.... 10)
  •   
     

姓名(简称)

     
      
  • 100000 - A
  •   
  • 101000 - a
  •   
  • 101010 - 我
  •   
     

名称(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;
    }
}

但它有很多限制。

  • 最有可能的是,我需要getFullQualifiedName()来在UI上显示它,但每次我都需要额外调用来填充所有名称。
  • 对于每个实体都有CodeName作为其子项,无论codeName有多深,我都必须扩展到codeName并使用FQN重新加载。

我们可以直接通过JPA映射所有@Transient名称吗?

2 个答案:

答案 0 :(得分:2)

您可以在技术上为您的代码存储库实体建模,如下所示:

 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,因此您基本上可以从任何子getFullyQualifiedName实体开始,并将它的父/子关系遍历到根。这基本上允许@Column private String fullyQualifiedName方法在运行时为您构建名称。

如果执行此操作时性能成为问题,您可以随时通过添加<meta name="viewport" content="width=device-width; initial-scale=0.4;" /> 来确定实体中的名称,并确保在创建代码时插入字段。然后我可以删除我添加到实体中的瞬态方法,因为您在数据插入时缓存了名称。

答案 1 :(得分:1)

可以编写一个JPQL,它等同于您的SQL查询。唯一棘手的部分是将嵌套选择重写为交叉连接,因为JPA不支持嵌套选择,您需要加入不相关的实体。另一方面,JPQL支持函数CONCATSUBSTRING,方式与SQL相同。请参阅以下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]);
}