如何将手动SQL行映射与CrudRepository混合(Spring数据)

时间:2018-08-13 20:08:15

标签: java spring hibernate spring-data-jpa spring-data

上下文

我已将应用程序分为三层:控制器,服务,存储库。这非常有用,因为我可以在任何层上进行更改而不会影响其他层。

当前,我正在尝试优化一些查询,在某些地方实体图和生成的查询不足以满足我的需求。通过编写本机SQL并映射结果集,我自己使用了现有的@Entity,相信可以更快地生成数据。

问题

鉴于我有一些界面:

public interface UserRepository extends CrudRepository<User, Long> {}

如何扩展UserRepository使其既使用CrudRepository方法(例如save()findById())又使用我自己的本机查询方法。

我正在使用Spring Boot在Java 8中的每个租户数据库环境中使用hibernate作为持久性提供程序。我考虑过以下解决方案,但还不满意:

  • JPQL-在某些地方它非常有用,并且可以节省很多样板。但是对于复杂的查询或想充分利用数据库规范化的地方,我发现它很难使用,而是拥有完全的控制权。
  • @Query中的NativeQuery-要求在实体类中包含巨大的@PostConstruct批注。
  • Spring数据查询语言-与使用@Entity图(我发现深度有限)时类似。

我在Spring Data Docs中找不到任何描述如何执行此操作的信息,但是我敢肯定,我已经在网上某处看到了这样的解决方案。

为清楚起见,我希望这样做:

public interface UserRepository extends CrudRepository<User, Long> {};

public class UserRepositoryImpl extends UserRepository {

    public User doComplexQuery() {

        List<Result> rows = query.("select * from ... ");
        User user = new User(rows.get(0));
        return user;

    }

};

public class UserService {
    @Autowired private UserRepository userRepository;
    public User getUser() {
         return userRepository.doComplexQuery();
    }

    public User saveUser(User user) {
         return userRepository.save(user);
    }
}

2 个答案:

答案 0 :(得分:1)

对我来说,最好和最简单的选择是将您描述的所有选项结合在一起,用projections(如果我正确理解您的话)。

我使用Lombok

创建简单的实体
@Data
@Builder
@NoArgConstructor
@AllArgsConstructor
@Entity
public class Model implements Serializable {
    //...       

    private String name;

    @OneToMany  
    private List<Subject> subjects;
}

@Data
@Builder
@NoArgConstructor
@AllArgsConstructor
@Entity
public class Subject implements Serializable {
    //...
}

然后定义必要的投影,例如:

public interface ModelSubjects {
    List<Subject> getSubjects();
}

public interface SomeProjection {
    //...
}

然后根据需要扩展我的仓库:

public interface ModelRepo extends JpaRepository<Model, Long> {
    @Query("select distinct m from Model m join fetch m.subjects where ...")
    List<Model> getWithJPQLQuery(...);

    @Query("select s as subjects from Model m join m.subjects s where ...")
    List<ModelSubjects> getSubjectsWithJPQLQuery(...);

    @Query(value = "some complex SQL query...", nativeQuery = true)
    List<SomeProjection> getWithSQLQuery(...);

    @EntityGraph(attributePaths = "subjects")
    List<Model> findByName(String name);    
}

已更新

复杂的示例(请参见答案下方的注释):

@Entity
public class Forest {
    //...

    private String name;

    @OneToMany
    private List<Tree> trees;
}

@Entity
public class Tree {
    //...

    @OneToMany
    private List<Branch> branches;
}

@Entity
public class Branch {
    //...
}
public interface BranchNumberByForest {
    String getForestName();
    Long getBranchNumber();
}

本地查询

public interface ForestRepo extends JpaRepository<Forest, Long> {
    @Query(value = "" +
        "select " + 
        "  f.name as forestName, " + 
        "  count(b.*) as branchNumber " +
        "from " + 
        "  forests f " + 
        "  left join trees t on t.forest_id = f.id " +
        "  left join branches b on b.tree_id = t.id " +
        "group by " +
        "  f.name", nativeQuery = true)
    List<BranchNumberByForest> getBranchNumbers();
}

与JPQL相同

public interface ForestRepo extends JpaRepository<Forest, Long> {
    @Query("" +
        "select " + 
        "  f.name as forestName, " + 
        "  count(b) as branchNumber " +
        "from " + 
        "  Forest f " + 
        "  left join f.trees t " +
        "  left join t.branches b " +
        "group by " +
        "  f.name")
    List<BranchNumberByForest> getBranchNumbers();
}

答案 1 :(得分:1)

我认为您正在寻找的是custom method implementations

请注意,您可以将EntityManager或任何其他Spring Bean注入到实现类中。

因此解决方案看起来类似于:

public interface UserRepository extends CrudRepository<User, Long>, CustomUserRepository {};

public interface CustomUserRepository {
    User doComplexQuery();
};

public class CustomUserRepositoryImpl implements UserRepository {

    @Autowired
    EntityManager em;

    public User doComplexQuery() {

        List<Result> rows = em.createNativeQuery("select * from ... ");
        User user = new User(rows.get(0));
        return user;

    }
};