実現したいこと
現在java H2データベースを使用してTwitter(X)のようなつぶやきアプリの制作をしています。
画像を格納するテーブルと、テキストを格納するテーブルの2つがあります。
画像とテキストデータを関連付けて表示させたいです。(関連付けて投稿したつもりでも識別子がうまく付与されずにテキストのみ、と画像のみという判定になってしまう)
発生している問題・分からないこと
ユーザー情報登録→それを使用してログイン→メイン画面(実際につぶやきをするタイムライン)
ログインまではできており、実際に送信をすることでテキストと画像データもデータベースへと格納までできています。コメントを送信するとタイムラインに新しいものから順に表示され、画像も同じように表示されます。しかし、どうしてもテキストと画像を関連付けて表示することができません。
テーブルにも参照整合性制約を持たせてもみましたが、制約違反が解決できず現在は制約をはずしました。画像は格納されても、テキストテーブルと同じIDを挿入できません。nullと表示されてしまいます。
なぜnullになってしまうのか、なぜ制約をもたせると違反になってしまうのか、その解決と、テキストと画像を効率的に関連付ける方法があれば教えていただきたいです。
先週の金曜日から今日まで50時間以上同じところで悩んでいます。できれば一人で解決したかったのですが、ここ二日は考えすぎて睡眠もとれず徹夜状態で頭がおかしくなりそうで相談させていただくことにしました。よろしくお願いします。
プログラミングをはじめて2か月にも満たない初心者で突っ込みどころの多いコードだと思います。
他にロジッククラスやBOクラスがありますがそちらは特に問題ではないと思っていますが、非常に多いので、問題のありそうだと思われるところを抜粋しますがもし足りない情報があれば追加します。
上記の画像のように表示されてしまっていました。このテキストと画像を同時に投稿した場合には独立せず同じ枠内で表示したかったのです。SELECTクエリではA,B,Cそれぞれをデータベース上で確認をして、全てのデータを重複せずに取得できることを確認しています。
この後行った修正は、残っている原因として考えられるものはA,B,Cで取得したレコードを全て一つのリストにまとめていましたが、これを順番に取り出す過程で関連させたい画像とテキストデータが結びついていないからでは?という発想に至り、Mapオブジェクトを作成してみることにしました。これでそれぞれA,B,Cから取得したレコードをMapオブジェクトにしてから一つのリストにまとめましたがこれでも表示は変わりませんでした。
データベースからの取得してくるクエリで、UNIONというものを教えてもらい、それでやってみたら思い通りの表示が可能になりました。以下使用したクエリ
String queryA = "SELECT * FROM (";
queryA += "SELECT m.mutter_id AS mutter_id, m.name AS name, m.text AS text, m.tweet_date AS tweet_date, f.name AS file_name, f.content AS content, f.content_type AS content_type, f.uploaded_at AS uploaded_at ";
queryA += "FROM MUTTERS m ";
queryA += "LEFT JOIN FILEPATHS f ON m.mutter_id = f.mutter_id ";
queryA += "UNION ";
queryA += "SELECT m.mutter_id AS mutter_id, f.name AS name, m.text AS text, m.tweet_date AS tweet_date, m.name AS file_name, f.content AS content, f.content_type AS content_type, f.uploaded_at AS uploaded_at ";
queryA += "FROM MUTTERS m ";
queryA += "RIGHT JOIN FILEPATHS f ON m.mutter_id = f.mutter_id ";
queryA += ") AS result ORDER BY COALESCE(tweet_date, uploaded_at) DESC";
このクエリを使用しただけで表示が改善されたので、データベースからの取得が間違っていたのだと思います。(まだ具体的に以前使用していたクエリの問題点ははっきりしていません)
この後、取得したレコードの表示順でまた問題が発生したため最後の AS result ORDER BY COALESCE(tweet_date, uploaded_at) DESC";というものを調べて外側で並び替えを行えるというものだったのでこれを使用したら並び替えもうまくできました。
該当のソースコード
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // リクエストパラメータの取得 request.setCharacterEncoding("UTF-8"); String text = request.getParameter("text"); request.setCharacterEncoding("UTF-8"); List<String> images = new ArrayList<String>(); // postされたパラメータの確認 String name = request.getParameter("name"); Part filePart = request.getPart("file1"); if (!text.isEmpty() && filePart != null && filePart.getSize() > 0) { // セッションスコープに保存されたユーザー情報を取得 HttpSession session = request.getSession(); User loginUser = (User) session.getAttribute("loginUser"); // つぶやきをつぶやきリストに追加 Mutter mutter = new Mutter(loginUser.getName(), text); PostMutterLogic postMutterLogic = new PostMutterLogic(); postMutterLogic.execute(mutter); // 画像のアップロード処理 String filename = makeUploadedFileName(filePart); InputStream fileContent = filePart.getInputStream(); String contentType = filePart.getContentType(); Image image = new Image(filename, fileContent, contentType); PostImageLogic postImageLogic = new PostImageLogic(); try { postImageLogic.execute(image); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }else if (text == null || text.isEmpty()) { // 画像のアップロード処理 String filename = makeUploadedFileName(filePart); InputStream fileContent = filePart.getInputStream(); String contentType = filePart.getContentType(); Image image = new Image(filename,fileContent,contentType); PostImageLogic postImageLogic = new PostImageLogic(); try { postImageLogic.execute(image); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } else if (!text.isEmpty()){ // セッションスコープに保存されたユーザー情報を取得 HttpSession session = request.getSession(); User loginUser = (User) session.getAttribute("loginUser"); // つぶやきをつぶやきリストに追加 Mutter mutter = new Mutter(loginUser.getName(), text); PostMutterLogic postMutterLogic = new PostMutterLogic(); postMutterLogic.execute(mutter); }
public class ImageDAO { // データベース接続に使用する情報 private final static String JDBC_URL = "jdbc:h2:tcp://localhost/~/dokoTsubuUp"; private final static String DB_USER = "sa"; private final static String DB_PASS = ""; //画像のみの投稿の処理 public boolean create(Image image) { try (Connection conn = DriverManager.getConnection(JDBC_URL, DB_USER, DB_PASS)) { String sql = "INSERT INTO FILEPATHS (content, content_type, uploaded_at) VALUES (?, ?, ?)"; try (PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setBlob(1, image.getFileContent()); pstmt.setString(2, image.getContentType()); pstmt.setTimestamp(3, Timestamp.valueOf(LocalDateTime.now())); int result = pstmt.executeUpdate(); return result == 1; } } catch (SQLException e) { e.printStackTrace(); return false; } } //画像とテキストが投稿された場合の処理 public boolean insertTextAndImage(Mutter mutter, Image image) { try (Connection conn = DriverManager.getConnection(JDBC_URL, DB_USER, DB_PASS)) { conn.setAutoCommit(false); // トランザクションを開始 // テキストを挿入 String insertMutterSQL = "INSERT INTO MUTTERS (name, text, tweet_date) VALUES (?, ?, ?)"; try (PreparedStatement pstmtMutter = conn.prepareStatement(insertMutterSQL, Statement.RETURN_GENERATED_KEYS)) { pstmtMutter.setString(1, mutter.getUserName()); pstmtMutter.setString(2, mutter.getText()); pstmtMutter.setDate(3, new java.sql.Date(System.currentTimeMillis())); int affectedRows = pstmtMutter.executeUpdate(); if (affectedRows == 0) { conn.rollback(); // ロールバック return false; } try (ResultSet generatedKeys = pstmtMutter.getGeneratedKeys()) { if (generatedKeys.next()) { int mutterId = generatedKeys.getInt(1); // 画像を挿入 String insertImageSQL = "INSERT INTO FILEPATHS (mutter_id, content, content_type) VALUES (?, ?, ?)"; try (PreparedStatement pstmtImage = conn.prepareStatement(insertImageSQL)) { pstmtImage.setInt(1, mutterId); pstmtImage.setBlob(2, image.getFileContent()); pstmtImage.setString(3, image.getContentType()); affectedRows = pstmtImage.executeUpdate(); if (affectedRows == 0) { conn.rollback(); // ロールバック return false; } } } } } conn.commit(); // コミット return true; } catch (SQLException e) { e.printStackTrace(); return false; } } }
public int create(Mutter mutter) { int mutterId = -1; // 初期値として無効なIDを設定 // JDBCドライバを読み込む try { Class.forName("org.h2.Driver"); } catch (ClassNotFoundException e) { throw new IllegalStateException("JDBCドライバを読み込めませんでした"); } // データベース接続 try (Connection conn = DriverManager.getConnection(JDBC_URL, DB_USER, DB_PASS)) { // INSERT文の準備 String sql = "INSERT INTO MUTTERS(name, text, tweet_date) VALUES (?, ?, ?)"; PreparedStatement pStmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); // INSERT文中の「?」に使用する値を設定しSQLを完成 pStmt.setString(1, mutter.getUserName()); pStmt.setString(2, mutter.getText()); pStmt.setDate(3, new java.sql.Date(System.currentTimeMillis())); // タイムスタンプの取得と設定 // INSERT文を実行し、自動生成されたIDを取得する int result = pStmt.executeUpdate(); if (result == 1) { ResultSet generatedKeys = pStmt.getGeneratedKeys(); if (generatedKeys.next()) { mutterId = generatedKeys.getInt(1); // 自動生成されたIDを取得 } else { throw new SQLException("Failed to get generated keys."); } } } catch (SQLException e) { e.printStackTrace(); } return mutterId; // 挿入されたテキストのIDを返す } }
試したこと・調べたこと
- teratailやGoogle等で検索した
- ソースコードを自分なりに変更した
- 知人に聞いた
- その他
上記の詳細・結果
特に改善はみられず、むしろ最初のころより冗長になってる気もします…
他にはAIを使用してエラーチェックなど行っています。
補足
Tomcat9_java17
Eclipse使用