如何使用Spring Projections和Join實現DTO?
SQL JOINS和DTO是我們打擊N + 1問題的好辦法,可參考DTO標籤看看其他方式,這裡,我們透過使用Spring Projections(DTO)和透過JPQL和本機SQL(用於MySQL)編寫各種Join聯接的概念證明。
1. 定義多個實體(例如,Tournament以及Player的雙向@OneToMany關係)
@Entity @Table(name = "tournaments") public class Tournament implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "tournament_name") private String tname; @OneToMany(cascade = CascadeType.ALL, mappedBy = "tournament", orphanRemoval = true) private List<Player> players = new ArrayList<Player>(); |
@Entity @Table(name = "players") public class Player implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "player_rank") private int prank; @Column(name = "player_name") private String pname; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "tournament_id") private Tournament tournament; |
2. application配置:
spring.datasource.url=jdbc:mysql://localhost:3306/db_tennis?createDatabaseIfNotExist=true spring.datasource.username=root spring.datasource.password=root spring.jpa.hibernate.ddl-auto=create spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect spring.datasource.initialization-mode=always spring.datasource.platform=mysql spring.jpa.open-in-view=false |
使用一些測試資料填充資料庫(例如,檢查檔案resources/data-mysql.sql)
3.編寫介面(projection)包含了需要從資料庫相關欄位的getters方法(例如TournamentPlayerNameDto,PlayerRankNameDto,TournamentIdNameDto)
public interface PlayerRankNameDto { String getPname(); int getPrank(); } public interface TournamentPlayerNameDto { String getPname(); String getTname(); } public interface TournamentIdNameDto { Long getId(); String getTname(); } |
4. 使用JPQL / native SQL編寫內部聯接查詢:
- 查詢所有玩家的錦標賽
(localhost:8080/tournamentsOfPlayersNamesInnerJoinJpql)
- 查詢所有排名小於或等於“ 排名 ”的玩家的錦標賽
(localhost:8080/tournamentsIdNameByRankInnerJoinSql)
對應倉儲的內聯SQL:
@Repository @Transactional(readOnly = true) public interface TournamentRepository extends JpaRepository<Tournament, Long> { //Inner Joins // Query the tournaments of all players (JPQL) @Query(value = "SELECT a.pname as pname, b.tname as tname " + "FROM Player a JOIN a.tournament b") List<TournamentPlayerNameDto> fetchTournamentsOfPlayersNamesInnerJoinJpql(); // Query the tournaments of all players (SQL) @Query(value = "SELECT a.player_name as pname, b.tournament_name as tname " + "FROM tournaments b INNER JOIN players a ON b.id = a.tournament_id", nativeQuery = true) List<TournamentPlayerNameDto> fetchTournamentsOfPlayersNamesInnerJoinSql(); // Query all tournaments that have players with rank smaller or equal to "rank" (JPQL) // Note: HINT_PASS_DISTINCT_THROUGH - doesn't work with Spring Projections @Query(value = "SELECT DISTINCT b.id as id, b.tname as tname " + "FROM Player a JOIN a.tournament b WHERE a.prank <= ?1") List<TournamentIdNameDto> fetchTournamentsIdNameByRankInnerJoinJpql(int rank); // Query all tournaments that have players with rank smaller or equal to "rank" (SQL) // Note: HINT_PASS_DISTINCT_THROUGH - doesn't work with Spring Projections @Query(value = "SELECT DISTINCT b.id as id, b.tournament_name as tname " + "FROM tournaments b INNER JOIN players a ON b.id = a.tournament_id " + "WHERE a.player_rank <= ?1", nativeQuery = true) List<TournamentIdNameDto> fetchTournamentsIdNameByRankInnerJoinSql(int rank); } |
原始碼可以在這裡找到 。
左連線 :
@Repository
@Transactional(readOnly = true)
public interface TournamentRepository extends JpaRepository<Tournament, Long> {
//Left Joins
// Query all tournaments even if they don't have players (JPQL)
@Query(value = "SELECT a.pname as pname, b.tname as tname "
+ "FROM Tournament b LEFT JOIN b.players a")
List<TournamentPlayerNameDto> fetchAllTournamentsLeftJoinJpql();
// Query all tournaments even if they don't have players (SQL)
@Query(value = "SELECT a.player_name as pname, b.tournament_name as tname "
+ "FROM tournaments b LEFT JOIN players a ON b.id = a.tournament_id",
nativeQuery = true)
List<TournamentPlayerNameDto> fetchAllTournamentsLeftJoinSql();
}
@Repository @Transactional(readOnly = true) public interface TournamentRepository extends JpaRepository<Tournament, Long> { //Right Joins // Query all tournaments even if they don't have players (JPQL) @Query(value = "SELECT a.pname as pname, b.tname as tname " + "FROM Player a RIGHT JOIN a.tournament b") List<TournamentPlayerNameDto> fetchAllTournamentsRightJoinJpql(); // Query all tournaments even if they don't have players (SQL) @Query(value = "SELECT a.player_name as pname, b.tournament_name as tname " + "FROM players a RIGHT JOIN tournaments b ON b.id = a.tournament_id", nativeQuery = true) List<TournamentPlayerNameDto> fetchAllTournamentsRightJoinSql(); } |
@Repository @Transactional(readOnly = true) public interface TournamentRepository extends JpaRepository<Tournament, Long> { //Full Joins // Query all tournaments and players (JPQL) @Query(value = "SELECT a.pname as pname, b.tname as tname " + "FROM Tournament b FULL JOIN b.players a") List<TournamentPlayerNameDto> fetchAllTournamentsAndPlayersFullJoinJpql(); // Query all tournaments and players (SQL) @Query(value = "SELECT a.player_name as pname, b.tournament_name as tname " + "FROM tournaments b FULL JOIN players a ON b.id = a.tournament_id", nativeQuery = true) List<TournamentPlayerNameDto> fetchAllTournamentsAndPlayersFullJoinSql(); } |
@Repository @Transactional(readOnly = true) public interface TournamentRepository extends JpaRepository<Tournament, Long> { //Left Excluding Joins // Query all tournaments that don't have players (JPQL) @Query(value = "SELECT a.pname as pname, b.tname as tname " + "FROM Tournament b LEFT JOIN b.players a WHERE a.id IS NULL") List<TournamentPlayerNameDto> fetchAllTournamentsLeftExcludingJoinJpql(); // Query all tournaments that don't have players (SQL) @Query(value = "SELECT a.player_name as pname, b.tournament_name as tname " + "FROM tournaments b LEFT JOIN players a ON b.id = a.tournament_id WHERE a.id IS NULL", nativeQuery = true) List<TournamentPlayerNameDto> fetchAllTournamentsLeftExcludingJoinSql(); } |
右排除Joins
@Repository @Transactional(readOnly = true) public interface TournamentRepository extends JpaRepository<Tournament, Long> { //Right Excluding Joins // Query all tournaments that don't have players (JPQL) @Query(value = "SELECT a.pname as pname, b.tname as tname " + "FROM Player a RIGHT JOIN a.tournament b WHERE a.id IS NULL") List<TournamentPlayerNameDto> fetchAllTournamentsRightExcludingJoinJpql(); // Query all tournaments that don't have players (SQL) @Query(value = "SELECT a.player_name as pname, b.tournament_name as tname " + "FROM players a RIGHT JOIN tournaments b ON b.id = a.tournament_id WHERE a.id IS NULL", nativeQuery = true) List<TournamentPlayerNameDto> fetchAllTournamentsRightExcludingJoinSql(); } |
@Repository @Transactional(readOnly = true) public interface TournamentRepository extends JpaRepository<Tournament, Long> { // Outer Excluding Joins // Query all tournaments that don't have players // and all players that don't participate in tournaments (JPQL) @Query(value = "SELECT a.pname as pname, b.tname as tname " + "FROM Player a FULL JOIN a.tournament b WHERE a.id IS NULL OR b.id IS NULL") List<TournamentPlayerNameDto> fetchAllTournamentsWithoutPlayersAndViceversaOuterExcludingJoinJpql(); // Query all tournaments that don't have players // and all players that don't participate in tournaments (SQL) @Query(value = "SELECT a.player_name as pname, b.tournament_name as tname " + "FROM players a FULL JOIN tournaments b ON b.id = a.tournament_id " + "WHERE a.id IS NULL OR b.id IS NULL", nativeQuery = true) List<TournamentPlayerNameDto> fetchAllTournamentsWithoutPlayersAndViceversaOuterExcludingJoinSql(); } |
相關文章
- 如何使用Spring Boot,Spring Data和H2 DB實現REST APISpring BootRESTAPI
- 使用Spring Boot和Kafka Streams實現CQRSSpring BootKafka
- Flink SQL 如何實現資料流的 Join?SQL
- 如何透過SqlResultSetMapping和NamedNativeQuery生成DTO?SQLAPP
- 分散式資料庫下子查詢和 Join 等複雜 SQL 如何實現?分散式資料庫SQL
- 使用Java和Spring Retry實現重試機制JavaSpring
- 使用 Spring Boot 3.2 和 CRaC 實現更快啟動Spring Boot
- 如何實現Spring Boot和Quartz整合? - Nguyen Phuc HaiSpring BootquartzAI
- 如何透過Spring Data/EntityManager/Session直接獲取DTO資料?SpringSession
- (譯)使用Spring Boot和Axon實現CQRS&Event SourcingSpring Boot
- 使用Spring Cloud Sleuth和OpenTelemetry實現分散式跟蹤SpringCloud分散式
- 使用Spring Boot, Istio和Cert Manager實現Kubernetes的HTTPSSpring BootHTTP
- 談談fork/join實現原理
- python技巧-使用os.path.join和os.path.sep.joinPython
- jmespath(2)投影ProjectionsProject
- Spring如何實現可插拔配置?Spring
- 如何在Spring中使用JobRunr實現後臺作業? - BaeldungSpring
- 如何透過javax.persistence.Tuple和JPQL提取DTO?Java
- 如何透過建構函式和JPQL生成DTO?函式
- 如何透過ResultTransformer和原生SQL或JPQL生成DTO?ORMSQL
- MySQL Join的底層實現原理MySql
- 使用MySQL的遞延Join連線實現高效分頁 - AaronMySql
- LEFT JOIN 和JOIN 多表連線
- sql:left join和join區別SQL
- mysql的left join和inner join的效率對比,以及如何優化MySql優化
- 使用Spring Cloud Stream和RabbitMQ實現事件驅動的微服務SpringCloudMQ事件微服務
- GitHub - soooban/AxonDemo: 使用Axon/Spring Cloud實現事件溯源和CQRS案例GithubSpringCloud事件
- Spring中使用MDC和traceId實現日誌鏈路追蹤Spring
- 使用Spring Boot、Kotlin和OpenFeign實現型別安全API測試Spring BootKotlin型別API
- 如何實現一個簡易版的 Spring - 如何實現 AOP(中)Spring
- 如何實現一個簡易版的 Spring - 如何實現 AOP(上)Spring
- 如何實現一個簡易版的 Spring - 如何實現 Setter 注入Spring
- Mysql join 的本質,Nested Loop Join 和 Hash JoinMySqlOOP
- 使用Spring Boot實現的GraphQL示例Spring Boot
- 使用HazelCast實現Spring Config Server配置ASTSpringServer
- 使用Spring實現上傳檔案Spring
- 使用Spring Data JDBC實現DDD聚合SpringJDBC
- 使用Spring Boot實現模組化Spring Boot