問題
JIRAやRedmineのようなバグトラッキングシステムを例とします。このシステムでは、トピックごとにプロジェクトを作成します。チケットはプロジェクトごとに作成され、利用者はチケットにコメントを付与することができるとします。ER図は以下の通りです。
データについては、次のようになっているとします。
ここで、プロジェクトとそれにひもづくチケット・コメントをすべて取得することを考えます。クエリを書くことは簡単ですが、Project.java
というネストされたJava Beanに結果をマッピングした場合、どのようにすればよいでしょうか? 言い換えれば、1対多の関係にあるテーブルに対する照会クエリをネストされたBeanにマッピングしたい場合、どのようにすればよいでしょうか?
Project.java
@Data
@ToString(exclude = {"tickets"})
public class Project {
private int projectId;
private String name;
private List<Ticket> tickets;
}
Ticket.java
@Data
@ToString(exclude = {"comments"})
public class Ticket {
private int ticketId;
private String content;
private List<Comment> comments;
}
Comment.java
@Data
public class Comment {
private int commentId;
private String content;
}
回答
resultMap
とcollection
という機能を利用します。 上記の例であれば、以下のようなxmlを作成します。
ProjectMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ProjectMapper">
<select id="findAllProject" resultMap="findAllProjectResultMap">
SELECT
project.project_id,
project.name as project_name,
ticket.ticket_id,
ticket.content as ticket_content,
comment.comment_id,
comment.content as comment_content
FROM project
JOIN ticket ON project.project_id = ticket.project_id
JOIN comment ON ticket.ticket_id = comment.ticket_id
</select>
<resultMap id="findAllProjectResultMap" type="com.example.demo.dto.Project">
<id property="projectId" column="project_id" />
<result property="name" column="project_name"/>
<collection property="tickets" ofType="com.example.demo.dto.Ticket">
<id property="ticketId" column="ticket_id" />
<result property="content" column="ticket_content"/>
<collection property="comments" ofType="com.example.demo.dto.Comment">
<id property="commentId" column="comment_id" />
<result property="content" column="comment_content"/>
</collection>
</collection>
</resultMap>
</mapper>
以下のコードではSQLを実行結果がどのような形でJava BeanにマッピングされたのかをPretty Printすることで、確認することができます
CommandlineappliApplication.java
@Component
public class CommandlineappliApplication implements CommandLineRunner{
@Autowired
private ProjectMapper projectMapper;
@Override
public void run(String... args) throws Exception {
for (Project project : projectMapper.findAllProject()) {
System.out.printf("%s%n", project);
for (Ticket ticket : project.getTickets()) {
System.out.printf(" └ %s%n", ticket);
for (Comment comment : ticket.getComments()) {
System.out.printf(" └ %s%n", comment);
}
}
}
}
}
実際に動かした結果は次の通り。ネストされたBeanに対して、SQLの実行結果が想定通りマッピングされていることがわかります。
Project(projectId=1, name=プロジェクト1)
└ Ticket(ticketId=1, content=チケット1-1)
└ Comment(commentId=1, content=コメント1-1-1)
└ Comment(commentId=2, content=コメント1-1-2)
└ Ticket(ticketId=2, content=チケット1-2)
└ Comment(commentId=3, content=コメント1-2-1)
└ Comment(commentId=4, content=コメント1-2-2)
Project(projectId=2, name=プロジェクト2)
└ Ticket(ticketId=3, content=チケット2-1)
└ Comment(commentId=5, content=コメント2-1-1)
└ Comment(commentId=6, content=コメント2-1-2)
└ Ticket(ticketId=4, content=チケット2-2)
└ Comment(commentId=7, content=コメント2-2-1)
└ Comment(commentId=8, content=コメント2-2-2)
環境情報
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>mybatis-sample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mybatis-sample</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>