如何通过 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;
}
}
但是它有很多限制。
- 很可能,我需要 getFullQualifiedName(),以便在 UI 上显示它,但每次我都必须额外调用以填充所有名称。
- 对于每个实体都有
CodeName
作为其子实体,无论 codeName
有多深,我都必须扩展到 codeName
并使用 FQN 重新加载它。
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 相同的方式支持函数 CONCAT
和 SUBSTRING
。请参阅以下 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]);
}
给定类似邮政编码的分层 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;
}
}
但是它有很多限制。
- 很可能,我需要 getFullQualifiedName(),以便在 UI 上显示它,但每次我都必须额外调用以填充所有名称。
- 对于每个实体都有
CodeName
作为其子实体,无论codeName
有多深,我都必须扩展到codeName
并使用 FQN 重新加载它。
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 相同的方式支持函数 CONCAT
和 SUBSTRING
。请参阅以下 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]);
}