20220108のJavaに関する記事は3件です。

[java]標準入力とif文で簡単なプログラムを作ってみた。

アウトプット用!! 今回は標準入力と条件式で簡単なプログラムを作ろう!とのことでさっそくやっていく。 まず今回のプログラムの流れ ①キーボードで好きな沖縄料理を入力する。 ②条件式を用いて入力した文字と条件が一致したらtrueになって沖縄料理が食べられる。もし沖縄料理じゃないとfalseになり沖縄料理ではないよ!と表示される。 okinawa.java import java.util.Scanner; public class Okinawa { public static void main(String[] args) { System.out.println("------------------"); System.out.println("沖縄料理を食べよう!!"); System.out.println("何を食べたいですか?"); System.out.println("------------------"); Scanner scanner = new Scanner(System.in); String str = scanner.next(); if(str.equals("沖縄そば")) { System.out.println("浜家で" + str + "を食べました。"); } else if(str.equals("タコライス")) { System.out.println("キンタコで" + str + "を食べました。" ); } else if(str.equals("ステーキ")) { System.out.println("やっぱりステーキで" + str + "を食べました"); } else { System.out.println("沖縄料理ではないよ!"); } コンソール.java ------------------ 沖縄料理を食べよう!! 何を食べたいですか? ------------------ 沖縄そば 浜家で沖縄そばを食べました。 *沖縄そばと入力した場合、濱家で沖縄そばを食べましたと表示される。 ①キーボードからの入力を取得するためにScannerクラスを用意する。 ・import java.util.Scannerを記述する。 ・Scannerクラスのインスタンスを作成。コンストラクタの引数にSystem.inを指定する。これはキーボード入力から値を取得する。next() メソッドでキーボードからの入力待ちとなります。 ②条件式で入力された値と条件を比較する equlasメソッドは文字列の比較。 例えば沖縄そばとキーボードで入力したらはじめの条件式1と比較され、一致した場合trueを返し浜家で沖縄そばを食べました。となります 以上です。 おつかれさまでした! これからも楽しみながらプログラミングがんばります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MyBatis × SpringBoot チャットアプリでメッセージ・画像送信機能の作成

はじめに 今回は、自作のチャットアプリで主機能となるメッセージ・画像送信機能の実装を行なったので、実装にあたり詰まったところと解説をアウトプットしていこうと思います。 環境 SpringBoot 2.5.5 MySQL 5.6.51 MyBatis 2.2.0 thymeleaf ER図 テーブル定義 usersテーブル Column Type Options id(PK・FK) BIGINT NOT NULL email VARCHAR NOT NULL password VARCHAR NOT NULL password_conf VARCHAR NOT NULL role VARCHAR created_at DATETIME updated_at DATETIME roomsテーブル Column Type Options id(PK・FK) BIGINT NOT NULL room_name VARCHAR NOT NULL created_at DATETIME room_usersテーブル Column Type Options id(PK) BIGINT NOT NULL room_id(FK) BIGINT NOT NULL current_user_id(FK) BIGINT NOT NULL user_id BIGINT NOT NULL created_at DATETIME messagesテーブル Column Type Options content VARCHAR NOT NULL image LONGBLOB room_id(FK) BIGINT NOT NULL user_id(FK) BIGINT NOT NULL created_at DATETIME   updated_at DATETIME メッセージ送信機能実装 Entity TMessage.java @Data public class TMessages { private int id; private String content; private int roomId; private int userId; private byte[] image; @DateTimeFormat(pattern = "yyyy_MM_dd HH:mm:ss") private LocalDateTime createdAt; @DateTimeFormat(pattern = "yyyy_MM_dd HH:mm:ss") private LocalDateTime updatedAt; ここでの注意点は、imageフィールドの型をbyte[]とすることです。 画像データはbyte[]で扱うことが多いので、このように定義します。 Mapper MessageMapper.java @Mapper public interface MessageMapper { /**メッセージ登録*/ public int insertOneMessage(TMessages message); } MessageMapper.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とxmlのマッピング --> <mapper namespace="com.example.demo.repository.MessageMapper"> <!-- マッピング定義(messages) --> <resultMap type="com.example.demo.entity.TMessages" id="message"> <id column="id" property="id"></id> <result column="content" property="content"></result> <result column="image" property="image"></result> <result column="room_id" property="roomId"></result> <result column="user_id" property="userId"></result> <result column="created_at" property="createdAt"></result> <result column="updated_at" property="updatedAt"></result> </resultMap> <!-- メッセージ登録 --> <insert id="insertOneMessage"> insert into messages ( id, content, image, room_id, user_id, created_at, updated_at ) values ( #{id,jdbcType=INTEGER}, #{content,jdbcType=VARCHAR}, #{image,jdbcType=BLOB}, #{roomId,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER}, #{createdAt,jdbcType=TIMESTAMP}, #{updatedAt,jdbcType=TIMESTAMP} ) </insert> </mapper> ここは見たまんまかなと思います。 Service MessageService.java public interface MessageService { /**メッセージ登録*/ public void insertMessage(TMessages message, MessageForm form, @AuthenticationPrincipal UserDetailServiceImpll loginUser, int roomId); } MessageServiceImpl.java @Service public class MessageServiceImpl implements MessageService { @Autowired private MessageMapper mapper; @Transactional @Override public void insertMessage(TMessages message, MessageForm form, @AuthenticationPrincipal UserDetailServiceImpll loginUser, int roomId) { //フォームから入力値取得 message.setContent(form.getContent()); //チャットルームのID設定 message.setRoomId(roomId); //ログインユーザーのID取得 int userId = loginUser.getUser().getId(); //ログインユーザーID設定 message.setUserId(userId); //現在時刻の取得 LocalDateTime now = LocalDateTime.now(); message.setCreatedAt(now); message.setUpdatedAt(now); //メッセージ登録 mapper.insertOneMessage(message); } } ここも見たまんまなので、解説は割愛させていただきます。 なお、@AuthenticationPrincipalアノテーションの詳しい実装については、以下の記事にまとめてあるので、よかったら、読んでみてください SpringSecurityとSpringBootでログイン認証と投稿機能を実装する form MessageForm.java @Data public class MessageForm { @NotBlank private String content; //画像入力時 private MultipartFile multiPartFile; } こちらがフォームクラスになります。 普通のフォームクラスなら、入力値とエンティティのフィールド名を合わせますが、画像データの入力となるため、MultipartFile型を指定したフィールドを作成しています。 Controller MessageController.java @Controller @RequestMapping("/") @Slf4j public class MessageController { @Autowired private MessageService service; @Autowired private RoomService roomService; @PostMapping("/rooms/{roomId}/message") public String postMessage(Model model, TMessages message, @Validated @ModelAttribute("form") MessageForm form, BindingResult result, @AuthenticationPrincipal UserDetailServiceImpll loginUser, @PathVariable("roomId") int roomId) throws IOException { //チャットルーム1件取得 MRoom room = roomService.getRoomOne(roomId); if (result.hasErrors()) { //NG:メッセージ送信画面にリダイレクト return "redirect:/rooms/{roomId}"; } log.info(form.toString()); //画像データをフォームから取得し設定 message.setImage(form.getMultiPartFile().getBytes()); service.insertMessage(message, form, loginUser, room.getId()); return "redirect:/rooms/{roomId}"; } } 私の場合は、このコントローラー部分の実装で詰まりました。 というのも、messagesテーブルには、外部キーで参照しているroom_idの登録が必要だったため、その値の取得が中々うまくいかず、サービスクラスと行ったり来たりを繰り返してました(笑) ポイントとなるのは@PathVariableアノテーションの部分です。 より詳しく言うと、「roomsテーブルのレコード1件取得するようなロジックにしないといけない」ということです。 例えば、roomsテーブルの値を1件取得するだけなら、@PathVariable("id") int idという記述でもroomsテーブルの値自体は取得できます。 ただ、messagesテーブルに値を登録するとなると、上記の記述だと、1回目は登録できても、2回目のメッセージ登録時にエラーが出ます。 この理由としては、roomsテーブルのidカラム(PK)を参照しているがために、messagesテーブルのidカラム(PK)にも参照しているroomsテーブルのidカラムの値を登録してしまうためです。 つまり、一意であるはずの主キーカラムに同じ値が登録され続けてしまうという事態になり、Duplicate entry 'x(数値)' for key 'PRIMARY'とエラーが出ます。 ここで、チャットルーム詳細画面遷移のロジックを実装している、コントローラーを見てみましょう。 RoomController.java @Controller @RequestMapping("/") @Slf4j public class RoomController { @Autowired private UserService userService; @Autowired private RoomService roomService; @Autowired private RoomUserService roomUserService; @GetMapping("/rooms/{roomId}") public String getRoom(Model model, @AuthenticationPrincipal UserDetailServiceImpll loginUser, @PathVariable("roomId") int id, @ModelAttribute("form") MessageForm form) { //ログインユーザーの情報を取得 String username = loginUser.getUser().getName(); int loginUserId = loginUser.getUser().getId(); model.addAttribute("username", username); //ログインユーザーと選択されたユーザーが保有するチャットルームを取得 List<MRoom> rooms = roomService.getLoginUserRooms(loginUser); model.addAttribute("rooms", rooms); //room_usersテーブルのレコード(1件)取得 TRoomUser roomUser = roomUserService.getRoomUserOne(id); //room_usersに登録されているログインユーザーのIDを取得 int currentUserId = roomUser.getCurrentUserId(); //room_usersに登録されているチャットするユーザーのIDを取得 int userId = roomUser.getUserId(); //ログインユーザーとroom_usersのログインユーザーID、またはログインユーザーとチャット選択されたユーザーのIDが等しい時メッセー送信画面に遷移する if(loginUserId == currentUserId || loginUserId == userId) { return "redirect:/rooms/{roomId}"; } return "redirect:/"; } } MessageControllerと@PathVariableアノテーション部分を比較すると違っているのがわかると思います。 こちらは、Roomsのコントローラークラスであるために、@PathVariable("roomId") int idとしていますが、messagesのコントローラークラスでは@PathVariable("roomId") int roomIdとしています。 これで、明確に参照しているカラムを分けることができるため、主キーがダブるエラーは発生しなくなります。 具体的には、RoomControllerはroomsのidカラムを参照し、MessageControllerではmessageのroom_idカラムを参照するようにしています。 あとはサービスクラスのメソッドを呼び出せば機能実装が完了するわけですが、あと1つ注意点があります。 それは、サービスクラスのメソッド呼び出し時の引数に room.getId()を渡してあげることです。 さらにいうと、その前に、RoomServiceクラスのメソッドを呼び出し、roomsのレコードを1件取得するロジックを加える必要もあります。 そうしないと、メッセージ送信時にroom_idカラムがnullというエラーが出るので、記述する必要があります。 View main_chat.index <form class="form" th:action="@{'/rooms/'+${roomId}+'/message'}" method="post" th:object="${form}" enctype="multipart/form-data"> <div class="form-input"> <input th:field="*{content}" th:errorclass="is-invalid" class="form-message" placeholder="type a message"> <div class="invalid-feedback" th:errors="*{content}"></div> <label class="form-image"> <span class="image-file">画像</span> <input th:field="*{multiPartFile}" type="file" class="hidden"> </label> </div> <input class="form-submit" type="submit" name="commit" value="送信"> </form> ポイントは画像投稿の部分です。formタグのenctype属性をenctype="multipart/form-data"とすることで.pngなどの画像データを送信できるようになります。 あとは、formクラスで定義しているフィールドをinputタグに指定するだけで画像の登録はできます。 以上で解説は終了です。 最後までありがとうございました。 参考 ・Java SpringBootで、MultipartFileクラスを使いDBに画像を保持する ・springboot + mybatis で画像をDBへアップロードする方法 ・MySQLのカラム型(有効範囲と必要記憶容量)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

気ぃつけよ! JavaエンジニアがC#を学ぶ時につまずくポイント3選

こんにちは。 Javaを今まで書いていてここ2ヶ月でC#を書き始めたものです。 それにしてもJavaとC#って似てますよね? 「JavaとC#ってほぼ同じだし、1日で文法さらえたわ」 こんなふうに思っていたら、要注意です(自戒)。 似てるが故にハマる落とし穴というものもあるのです。 背景 聞くところによると、Javaが1995年に最初に登場して、C#が2000年に最初に登場したようです。 Javaの方が5年上の先輩ですね。 よって、C#にできるけどJavaではできない、みたいな言語機能もあるわけです。 まあでもお互いに影響し合って今まで成長してきたのでしょうね。 ここからは、実際にJavaとC#の相違点について考え、Javaエンジニアが特に陥りやすい言語仕様などを紹介していきたいと思います。 1. "protected" の意味が違う JavaをやっていてC#を始めて触る人は大概、「internalってなんだ?でもprotectedは共通してる!たぶん同じ意味だろうな」なんて思ったりするものです。 違います。 JavaとC#では、アクセス修飾子の意味合いはかなり違います。 (privateとpublicしか同じじゃないです) Java protected: 同じパッケージ内 or 継承クラス (修飾子なし): 同じパッケージ内 C# protected internal: 同じアセンブリ内 or 継承クラス internal: 同じアセンブリ内 protected: 継承クラス private protected: 同じアセンブリ内 and 継承クラス このように… 違うんですよね、意外と。 表にまとめてみるとこんな感じです。 アクセスしやすさ Java C# 詳細 1 public public どこでも 2 protected protected internal 同じパッケージ/アセンブリ内 or 継承クラス 3 (修飾子なし) internal 同じパッケージ/アセンブリ内 3 - protected 継承クラス 5 - private protected 同じアセンブリ内 and 継承クラス 6 private private 同じクラス内のみ こうみると、C#はJavaの悪い点をちゃんと改善していて良いなって思いますよね。 C#での「修飾子なし」のアクセスレベル またもう1つC#を学ぶ際に罠になるのが、C#での「修飾子なし」の場合です。 この場合は、アクセスレベルには「規定のアクセシビリティ」が付与されます。 しかしこの「規定のアクセシビリティ」、つまり「デフォルトのやつ」がものによって異なっているんですよね〜。 アクセスされるもの メンバのアクセス修飾子 class private struct private interface public enum public つまりこれらは一緒です。 public class TestClass { private int _privateInt1; int _privateInt2; } public struct TestStruct { private string _privateString1; string _privateString2; } public interface ITest { public void PublicFunction1(); void PublicFunction2(); } 覚え方としては、「考えられるアクセス修飾子の中で、一番厳しいもの」と覚えれば大丈夫です。interfaceやenumはそもそも普通は実装を持つものではないし、privateにする意味もないのでpublicになってる、と考えれば納得がいきます。 2. getHoge, setHogeなどを大量に書きがち Java上がりのエンジニアは、getter関数やsetter関数を大量に書く習性があります。 なぜなら、Javaで大事なメンバをpublicで宣言して…なんて事したら上司にこっ酷く怒られたからです。 なので普通は外部に公開したいメンバには基本的にgetterとsetterを書いて値の正当性を保持したりするのですが… C#erはあんまりSetHogeやGetHogeなんて書きません。代わりに「プロパティ」を使用します。 プロパティとは? getterとsetterが備わっているメンバみたいなもののことをいいます。 例えば以下の例。 Person.cs public class Person { private int _age; public int Age { internal get { return _age; } set { if (value < 0 || 150 < value) throw new System.ArgumentOutOfRangeException(); _age = value; } } } _ageというメンバは外からは見えません。しかしAgeというプロパティを通してそのメンバにアクセスすることができるようになります。 また、getterとsetterにはそれぞれ個別のアクセシビリティを指定することができます。上記の例では、getterはinternal, setterはpublicとなります。これによって歳を気にしだしたアラフォーのおばさんも、外部のアセンブリには自分の歳を非公開にすることができます(まあReflection使ったら見れるんですけどね)。 これによって、下手に冗長なコードを書かずにすみますし、綺麗ですよね。 でもsetHoge, getHogeを書きたくなるのはわかります…僕も抜け出せないでいました… 3. Javaにはない参照渡しがある…!! そうです。Javaにはない参照渡しが存在してしまっているのです。 Javaでは? Javaでは、関数の引数はいわゆる「値渡し」というものでした。正確に言えば、全てが「クラスのインスタンスへのポインタ」でした。なので、新しいnewしたインスタンスを返してあげたい時は絶対に返り値で戻す必要があったのです。 Java初心者じゃなければ以下の意味がわかるはずです。 ListRefModify.java public class ListRefModify { public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>(); list.add("original"); modify(list); System.out.println(list); } private static void modify(ArrayList<String> list) { list = new ArrayList<String>(); list.add("modified"); } } 出力: ["original"] ※なぜならlistはポインタの値であり、ポインタの指し示す値を変えているわけではないから C#では? C#ではJavaでは紛らわしかった、ポインタ渡しではなく、純粋な「参照渡し」ができます! C#では参照渡しのためにref, out, in という3つのキーワードを、実引数と仮引数の両方に使います。 ref ただの参照渡し。 public static void modify(ref int a) { a = 2; } public static void test() { int a = 1; modify(ref a); Console.WriteLine(a); // 2 } in 呼ばれた関数内で、readonlyとして使われる参照渡し。「見るだけ、お触りはだめ」的なノリ。 public static void modify(in int a) { // a = 2; // compile error int b = a; Console.WriteLine(b); // 0 } public static void test() { int a = 0; modify(in a); Console.WriteLine(a); // 0 } out 呼ばれた関数内のみで、絶対に初期化される参照渡し。「これになんかもの入れといて〜」的なノリ。 public static void modify(out int a) { a = 2; } public static void test() { int a; modify(out a); Console.WriteLine(a); // 2 } 問題解決…! これにより、先ほどのJavaの問題(つまり、呼び出し先でnewしたやつを入れてそれを反映させたいという欲求)が解消されます。ListRefModify.javaをC#で書き直してみます。 ListRefModify.cs public class ListRefModify { public static void Main(String[] args) { List<string> list = new List<string>(); list.Add("original"); Modify(ref list); Console.WriteLine(list); } private static void Modify(ref List<string> list) { list = new List<string>(); list.Add("modified"); } } 出力: ["modified"] C#はもともとクラスに関しては参照渡しですが、refをつけることで「参照の参照渡し」ができるようになります。これで、上記のように呼び出し先で違うインスタンスの参照を代入しても値が変更されないことがありません。 最初は多いように感じますが、使っていくたびにC#の参照渡しの多様性のありがたみに気づき、C#無しじゃ生きていけないようになり、そしてJavaが霞んで見えてくるでしょう おわり 今回はJavaに慣れている人が驚くであろう、そしてつまずくであろうポイントを3つ紹介しました。なぜかC#を称賛してJavaを卑下しそうになりましたが、Javaにだって良いところはあります。どっちも愛してあげようね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む