質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Hibernate

HibernateとはJava言語のobject-relational mapping (ORM)ライブラリであり、Object/Relational Mappingよりはるか多くの方法でアプリケーションをPOJOで機能付けることができます。

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

JSP

JSP(Java Server Pages)とは、ウェブアプリケーションの表示レイヤーに使われるサーバーサイドの技術のことです。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Eclipse

Eclipseは、IBM社で開発された統合開発環境のひとつです。2001年11月にオープンソース化されました。 たくさんのプラグインがあり自由に機能を追加をすることができるため、開発ツールにおける共通プラットフォームとして位置づけられています。 Eclipse自体は、Javaで実装されています。

Q&A

解決済

1回答

3542閲覧

Hibernateを使い、3つのテーブルを結合して表示させたいです。

yuki_mita

総合スコア5

Hibernate

HibernateとはJava言語のobject-relational mapping (ORM)ライブラリであり、Object/Relational Mappingよりはるか多くの方法でアプリケーションをPOJOで機能付けることができます。

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

JSP

JSP(Java Server Pages)とは、ウェブアプリケーションの表示レイヤーに使われるサーバーサイドの技術のことです。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Eclipse

Eclipseは、IBM社で開発された統合開発環境のひとつです。2001年11月にオープンソース化されました。 たくさんのプラグインがあり自由に機能を追加をすることができるため、開発ツールにおける共通プラットフォームとして位置づけられています。 Eclipse自体は、Javaで実装されています。

0グッド

1クリップ

投稿2019/12/26 07:36

編集2019/12/27 11:50

前提・実現したいこと

プログラミングを始めて三か月半の初心者です。

Hibernateを使って複数のエンティティからデータを検索する方法を知りたいです。

前提として、下記の三つのテーブルを使用します。{}がテーブル名、以下","で区切られているのがカラム名です。

  • {Highlight}

id, video_id, highlight, comment, user_id, created_at, updated_at, public_flag

  • {Video}

id, youtube_id, title, publishedAt, thumbnail, tag, taisyo

  • {User}

id, name, address, created_at, delete_flag

Highlightテーブルのvideo_idをVideoテーブルにあるidに、user_idをUserテーブルにあるidにそれぞれ結合させます。
VideoとHighlight、UserとHighlightがそれぞれ一対多の関係性です。

下記はJPQL文の一例です。

SELECT h.id, h.highlight, h.updated_at, h.public_flag, v.title, u.name, u.delete_flag FROM Highlight h JOIN Video v ON video_id = v.id JOIN User u ON user_id = u.id ORDER BY h.id DESC

しかし、ブラウザに以下のエラーが表示されてしまいます。

発生している問題・エラーメッセージ

HTTP ERROR 500 Problem accessing /highlights/index. Reason: Server Error Caused by: javax.persistence.PersistenceException: [PersistenceUnit: keso_hane_highlight] Unable to build Hibernate SessionFactory // 長すぎるので冒頭だけ載せています。

該当のソースコード

一覧の並び順を変えるため、JPQL文の内容を分岐させるクラスを作成しています。

package action.videos; public class SearchVideo { public static String IndexSortMethod(Integer n) { String[] isList = new String[4]; isList[0] = "v.id DESC"; isList[1] = "v.id ASC"; isList[2] = "v.title DESC"; isList[3] = "v.title ASC"; StringBuilder is = new StringBuilder("SELECT v.id, v.youtube_id, v.title, v.publishedAt, v.thumbnail FROM Video v ORDER BY "); is.append(isList[n]); String indexSort = is.toString(); // String indexSort = "SELECT v FROM Video AS v ORDER BY v.id DESC"; return indexSort; } }

一覧表示させるためのサーブレット

// ... /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { EntityManager em = DBUtil.createEntityManager(); int page = 1; try { page = Integer.parseInt(request.getParameter("page")); } catch(NumberFormatException e) { } int lines_per_page = 10; if (request.getSession().getAttribute("lines_per_page") != null) { lines_per_page = (Integer)request.getSession().getAttribute("lines_per_page"); request.getSession().removeAttribute("lines_per_page"); } try { lines_per_page = Integer.parseInt(request.getParameter("lines_per_page")); } catch(NumberFormatException e) { } int sortNumber = 0; if(request.getParameter("indexSort") != null && !request.getParameter("indexSort").equals("")) { sortNumber = Integer.parseInt(request.getParameter("indexSort")); } String sort = SearchVideo.IndexSortMethod(sortNumber); List<Video> videos = em.createQuery(sort, Video.class) .setFirstResult(lines_per_page * (page - 1)) .setMaxResults(lines_per_page) .getResultList(); long videos_count = (long)em.createNamedQuery("getVideosCount", Long.class) .getSingleResult(); em.close(); // ... }

検索したデータをそれぞれの変数に格納するコンストラクタ(Video)

// ... package models.videos; import java.sql.Blob; import java.sql.Timestamp; public class IndexVideo { private Integer id; private String youtube_id; private String title; private Timestamp publishedAt; private Blob thumbnail; public IndexVideo() {} public IndexVideo(Integer id, String youtube_id, String title, Timestamp publishedAt, Blob thumbnail) { this.id = id; this.youtube_id = youtube_id; this.title = title; this.publishedAt = publishedAt; this.thumbnail = thumbnail; } // 以下、getter/setter

結果を一覧表示させるビュー

// ... <c:import url="../layout/app.jsp"> <c:param name="content"> <h2><a href="<c:url value='/videos/index' />">けそポテトチャンネルの動画</a></h2> <form method="GET" action="<c:url value='/videos/index' />"> <select name="indexSort"> <option value="">並び替え</option> <option value="0">投稿日△</option> <option value="1">投稿日▽</option> <option value="2">タイトル△</option> <option value="3">タイトル▽</option> </select> <button type="submit">決定</button> </form> // ...

VideoテーブルのDTO

// ... @Entity public class Video { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "youtube_id", nullable = false, unique = true) private String youtube_id; @Column(name = "title", nullable = false) private String title; @Column(name = "publishedAt", nullable = false) private Timestamp publishedAt; @Column(name = "thumbnail", nullable = false) private Blob thumbnail; @Column(name = "tag") private String tag; @Column(name = "taisyo", nullable = false) private Integer taisyo; @OneToMany(mappedBy = "video") private List<Highlight> highlights; // 以下、getter/setter

HighlightテーブルのDTOクラス

// ... @Entity public class Highlight { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; // @Column(name = "video_id", nullable = false) // private Integer video_id; @Column(name = "highlight", length = 255, nullable = false, unique = true) private String highlight; @Column(name = "comment") private String comment; @Column(name = "highlight_time") private Time highlight_time; // @Column(name = "user_id") // private Integer user_id; @Column(name = "created_at", nullable = false) private Timestamp created_at; @Column(name = "updated_at", nullable = false) private Timestamp updated_at; @Column(name = "public_flag", nullable = false) private Integer public_flag; @ManyToOne private Video video; @ManyToOne private User user; // 以下、getter/setter

UserテーブルのDTOクラス

// ... @Entity public class User { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name = "name", nullable = false, unique = true) private String name; @Column(name = "address", nullable = false) private String address; @Column(name = "password", length = 64, nullable = false) private String password; @Column(name = "created_at", nullable = false) private Timestamp created_at; @Column(name = "delete_flag", nullable = false) private Integer delete_flag; @OneToMany(mappedBy = "user") private List<Highlight> highlights; // 以下、getter/setter

試したこと

それぞれのテーブルのDTOにOneToMany、ManyToOneのアノテーションを記述しました。

追記

別クラスに作成したコンストラクタを介さずに、単体のエンティティからデータを検索することはできました。しかし、コンストラクタを通すと単体のエンティティを検索してもエラーが表示されてしまいます。複数のエンティティの場合でも同様です。
つまり、コンストラクタが上手く機能していないようなのです。

HTTP ERROR 500 Problem accessing /videos/index. Reason: Server Error Caused by: java.lang.IllegalArgumentException: Cannot create TypedQuery for query with more than one return using requested result type [models.videos.IndexVideo] // ...

なお、OneToMany(VideoクラスとUserクラス)とManyToOne(Highlightクラス)のアノテーションをそれぞれDTOに記載するにあたって、Videoテーブルのidに対応するvideo_idカラムとUserテーブルのidに対応するuser_idを消しました(コメント状態にした部分です)。

最後に

質問が大変長く、コードの内容に至らない点が散見されると思いますが、ご回答のほどお待ちしております。

補足情報

IDE:eclipse
言語:Java
データベース:MySQL

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rubytomato

2019/12/27 07:07

> javax.persistence.PersistenceException: [PersistenceUnit: keso_hane_highlight] Unable to build Hibernate SessionFactory このエラーメッセージを見ると、クエリを実行する以前に何かしらのエラーが起きているように思いますが、 たとえば、複数のエンティティを結合しない単純なクエリは実行できるのでしょうか?
yuki_mita

2019/12/27 08:16

元々、単体のテーブルのみを扱うNamedQueryをそれぞれのDTOに書き、それをサーブレットで活用していました。 その時には問題なく動いていたのですが、複数のエンティティを結合したいと思い、さらに処理を分岐するために別のクラスにクエリを書くと、動かなくなりました。 今は単体のエンティティの場合でも、同様に動かなくなっています。
rubytomato

2019/12/27 08:45

> 複数のエンティティを結合したいと思い、さらに処理を分岐するために別のクラスにクエリを書くと、動かなくなりました。 > 今は単体のエンティティの場合でも、同様に動かなくなっています。 これだと回答するのが難しいので、少なくとも単体エンティティで検索できる状態にまで回復させてください。 おそらく、Highlightクラスに@ManyToOneのフィールドを追加したのだと思いますが、その場合は下記のフィールドを削除してください。 @Column(name = "video_id", nullable = false) private Integer video_id; @Column(name = "user_id") private Integer user_id; 単体エンティティで動くようになりましたら、その状態のソースコードを質問内容に反映して頂いて、改めて動作確認、エラーがでましたらそのエラー内容も追記してください。
yuki_mita

2019/12/27 11:55

質問の内容を更新させていただきました。恐縮ですが、引き続きご回答のほどよろしくお願いします。
rubytomato

2019/12/27 12:30

追記ありがとうございます。念の為確認させてください。 > 単体のエンティティからデータを検索することはできました。しかし、コンストラクタを通すと単体のエンティティを検索してもエラーが表示されてしまいます。 とのことですが、 ●検索できるパターン > List<Video> videos = em.createQuery(sort, Video.class) ●エラーが起きるパターン > List<IndexVideo> videos = em.createQuery(sort, IndexVideo.class) ということでしょうか? それとこのサーブレットの目的は、ビデオとそれに紐づくハイライトを一覧表示するということでしょうか?
yuki_mita

2019/12/27 12:39

検索できるパターンとエラーが起きるパターンは、rubytomatoさんにお書きいただけた通りです。コンストラクタであるIndexVideoクラスを使うとエラーが起きてしまいます。 サーブレットの目的は、ビデオとハイライトに加えて、ハイライトを投稿したユーザーの方のニックネームも合わせて表示させることです。ですので、3つのエンティティを結合させた上で検索する処理をしたいのです。
guest

回答1

0

ベストアンサー

最初のエラーの原因

javax.persistence.PersistenceException: [PersistenceUnit: keso_hane_highlight] Unable to build Hibernate SessionFactory

このエラーが起きていた直接の原因は、video_iduser_idを残したままで@ManyToOneアノテーションを付けたvideouserフィールドを追加したことです。
下記のように両方を定義することはできません。

Java

1@Entity 2public class Highlight { 3 4 @Column(name = "video_id", nullable = false) 5 private Integer video_id; 6 7 @Column(name = "user_id") 8 private Integer user_id; 9 10 @ManyToOne 11 private Video video; 12 13 @ManyToOne 14 private User user; 15 16}

修正内容

Highlight

Highlightに下記の修正を行いました(@JoinColumnアノテーションの追加)。他のエンティティクラスはそのままです。

Java

1@ManyToOne 2@JoinColumn(name = "video_id") 3private Video video; 4 5@ManyToOne 6@JoinColumn(name = "user_id") 7private User user;

IndexVideo

ビデオとハイライトに加えて、ハイライトを投稿したユーザーの方のニックネームも合わせて表示させることです。

ということなので、Video、Highlight、Userエンティティからビューへ渡したいフィールドを持つIndexVideoクラスを、仮に下記のように修正します。

Java

1public class IndexVideo { 2 // from Video 3 private Integer id; 4 private String youtube_id; 5 private String title; 6 private Timestamp publishedAt; 7 private Blob thumbnail; 8 9 // from Highlight 10 private String highlight; 11 private Timestamp updated_at; 12 private Integer public_flag; 13 14 // from User 15 private String name; 16 private Integer delete_flag; 17 18 public IndexVideo() {} 19 20 public IndexVideo(Integer id, String youtube_id, String title, Timestamp publishedAt, Blob thumbnail, 21 String highlight, Timestamp updated_at, Integer public_flag, String name, Integer delete_flag) { 22 this.id = id; 23 this.youtube_id = youtube_id; 24 this.title = title; 25 this.publishedAt = publishedAt; 26 this.thumbnail = thumbnail; 27 this.highlight = highlight; 28 this.updated_at = updated_at; 29 this.public_flag = public_flag; 30 this.name = name; 31 this.delete_flag = delete_flag; 32 } 33 34 // 以下、getter/setter 35}

クエリ

エンティティクラス上で各テーブルのリレーションを定義しているので、JPQLは単純です。

SELECT v FROM Video v

ただし、上記のJPQLだとパフォーマンス上の問題を起こす可能性がありますので、最終的には下記のようにしました。

SELECT v FROM Video v LEFT JOIN FETCH v.highlights h LEFT JOIN FETCH h.user

以下がクエリを組み立てるクラスのサンプルです。

Java

1public class SearchVideo { 2 3 static final String QUERY = "SELECT v FROM Video v LEFT JOIN FETCH v.highlights h LEFT JOIN FETCH h.user"; 4 5 static final Map<Integer, String> ORDER_COLUMNS = new HashMap<>() { 6 { 7 put(0, "v.id DESC"); 8 put(1, "v.id ASC"); 9 put(2, "v.title DESC"); 10 put(3, "v.title ASC"); 11 } 12 }; 13 14 public static String IndexSortMethod(Integer n) { 15 if (n == null) { 16 return QEURY; 17 } 18 return QUERY + " ORDER BY " + ORDER_COLUMNS.get(n); 19 } 20}

サーブレット

2番目のエラーの原因

java.lang.IllegalArgumentException: Cannot create TypedQuery for query with more than one return using requested result type [models.videos.IndexVideo]

この書き方がエラーになる理由は、createQueryメソッドの第2引数にはJPQLが返すクラスしか指定できないためです。
通常は@Entityというクラスアノテーションが付いているクラスになります。

なので、JPQLがSELECT v FROM Video vの場合、下記のクエリはエラーになります。

Java

1// NG 2List<IndexVideo> videos = em.createQuery(sort, IndexVideo.class);

下記のクエリはエンティティクラスであるVideoを指定しているので問題ありません。

Java

1// OK 2List<Video> videos = em.createQuery(sort, Video.class);

Videoエンティティが検索できれば、Highlightエンティティ、Userエンティティへはリレーションを持っているのでこれらの値にアクセスできます。
あとは、これらのエンティティからIndexVideoのコレクションを作成してビューへ渡せばいいと思います。

具体的には、下記のようなコードになると思います。

Java

1String sort = SearchVideo.IndexSortMethod(sortNumber); 2TypedQuery<Video> query = em.createQuery(sort, Video.class); 3List<Video> result = query.setFirstResult(lines_per_page * (page - 1)) 4 .setMaxResults(lines_per_page) 5 .getResultList(); 6 7List<IndexVideo> videos = result.stream() 8 .map(this::convert) 9 .flatMap(Collection::stream) 10 .collect(Collectors.toList());

検索したVideo、Highlight、User を IndexVideoへコンバートするメソッドのサンプルです。

Java

1private List<IndexVideo> convert(Video video) { 2 return video.getHighlights().stream() 3 .map(highlight -> { 4 return new IndexVideo( 5 video.getId(), 6 video.getYoutube_id(), 7 video.getTitle(), 8 video.getPublishedAt(), 9 video.getThumbnail(), 10 highlight.getHighlight(), 11 highlight.getUpdated_at(), 12 highlight.getPublic_flag(), 13 highlight.getUser().getName(), 14 highlight.getUser().getDelete_flag()); 15 }) 16 .collect(Collectors.toList()); 17}

これで、3つのエンティティを結合した結果をビューへ渡すことができると思います。

もう1つの対応方法

Highlightエンティティを駆動表にしても良い場合は、もう少し簡略化することができます。

IndexHighlight

Java

1public class IndexHighlight { 2 // from Video 3 private Integer id; 4 private String youtube_id; 5 private String title; 6 private Timestamp publishedAt; 7 private Blob thumbnail; 8 9 // from Highlight 10 private String highlight; 11 private Timestamp updated_at; 12 private Integer public_flag; 13 14 // from User 15 private String name; 16 private Integer delete_flag; 17 18 // 以下、getter/setter 19}

クエリ

Highlightエンティティを駆動表とした場合のJPQLはこのようになります。

SELECT h FROM Highlight h LEFT JOIN FETCH h.video LEFT JOIN FETCH h.user

Java

1TypedQuery<Highlight> query = em.createQuery(sort, Highlight.class); 2List<Highlight> result = query.setFirstResult(lines_per_page * (page - 1)) 3 .setMaxResults(lines_per_page) 4 .getResultList(); 5 6List<IndexHighlight> highlights = result.stream() 7 .map(this::convert) 8 .collect(Collectors.toList());

コンバートメソッドです。

Java

1private IndexHighlight convert(Highlight highlight) { 2 return new IndexHighlight( 3 highlight.getVideo().getId(), 4 highlight.getVideo().getYoutube_id(), 5 highlight.getVideo().getTitle(), 6 highlight.getVideo().getPublishedAt(), 7 highlight.getVideo().getThumbnail(), 8 highlight.getHighlight(), 9 highlight.getUpdated_at(), 10 highlight.getPublic_flag(), 11 highlight.getUser().getName(), 12 highlight.getUser().getDelete_flag()); 13}

パフォーマンスを気にしなくてもよければ、JPQLは次のように書くこともできます。

SELECT new com.example.demo.dto.IndexHighlight(h) FROM Highlight h

この場合は、SELECT句にnew com.example.demo.dto.IndexHighlight(h)と書くことができるので、createQueryメソッドの第2引数にIndexHighlightクラスを指定することができます。

String sort = SearchVideo.IndexSortMethod(sortNumber); TypedQuery<IndexHighlight> query = em.createQuery(sort, IndexHighlight.class); List<IndexHighlight> result = query.setFirstResult(lines_per_page * (page - 1)) .setMaxResults(lines_per_page) .getResultList();

IndexHighlightクラスにHighlightエンティティクラスを引数にとるコンストラクタを実装し、ここでコンバートします。

Java

1public class IndexHighlight { 2 3 // 省略 4 5 // コンストラクタでコンバートする 6 public IndexHighlight(Highlight highlight) { 7 this.id = highlight.getVideo().getId(); 8 this.youtube_id = highlight.getVideo().getYoutube_id(); 9 this.title = highlight.getVideo().getTitle(); 10 this.publishedAt = highlight.getVideo().getPublishedAt(); 11 this.thumbnail = highlight.getVideo().getThumbnail(); 12 13 this.highlight = highlight.getHighlight(); 14 this.updated_at = highlight.getUpdated_at(); 15 this.public_flag = highlight.getPublic_flag(); 16 17 this.name = highlight.getUser().getName(); 18 this.delete_flag = highlight.getUser().getDelete_flag(); 19 } 20 21 // 省略 22 23}

補足

質問内容のコードを見ていて気になった点がありましたので、最後に補足させて頂きます。
Javaではフィールド名はキャメルケース(ローワーキャメルケース)にすることが一般的です。

たとえば、下記のような書き方は

Java

1private Timestamp updated_at; 2private Integer public_flag;

このように書くのが一般的です。

Java

1private Timestamp updatedAt; 2private Integer publicFlag;

投稿2019/12/27 16:32

編集2019/12/27 19:48
rubytomato

総合スコア1752

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yuki_mita

2019/12/28 12:41

おかげさまで、問題なく動くようになりました! 丁寧に説明してくださり、助かりました。 ありがとうございます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問