完成品デモ
はじめに
こちらの記事を参考にして作成しております。すんなり作成できるかなと思っていたのですが、なかなか詰まったポイントもあったので、その点が差分となりますのでアウトプットします。
同じアプリを作成しましたが、使用しているDBやバージョンによって作り方を変更しないといけない点が多くその点勉強になりました。
開発環境
- OS:Windows
- DB:MySQL
- Eclipse:Version: 2022-12
- SpringBoot:3.3.4
- ビルドツール:Gradle
- Java17
SpringBootのアプリケーションを作成した事はあるが、ORマッパーを使用したアプリに関しては、業務で少し触った位であまり基礎から理解出来ていなかったため、作成しようと思いました。
ディレクトリの構成・ER図
DB作成/DDL・DML
-- todoアプリのデータベースを作成
CREATE DATABASE todo_db;
-- todoアプリのテーブルを作成
CREATE TABLE todo_items (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(50),
done_flg INT DEFAULT 0,
time_limit DATE NOT NULL
);
-- テストデータを挿入
INSERT INTO todo_items (id, title, done_flg, time_limit) VALUES (1, 'Javaの勉強をする', 1, '2024-10-25');
作成手順
Springスタータープロジェクト作成
依存関係は以下で設定
プロジェクトが作成出来たら、DBViewerで作成したDBを紐づける
※すみません、こちら割愛します。
mybatisとspringを連携する
こちらを設定しないと以下のエラーではまります。
外部DTD: accessExternalDTDプロパティで設定された制限により’http’アクセスが許可されていないため、外部DTD ‘mybatis-mapper-3.0.dtd’の読取りに失敗しました。
src/main/java/com/todoの下に、「config」パッケージを作成します。
「config」を右クリックし、「新規(W)」→「クラス」と進みます。
package com.example.todo.config;
import javax.sql.DataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
// @org.springframework.transaction.annotation.EnableTransactionManagementを付与し、
// アノテーション駆動(@Transactional)のトランザクション制御を有効にします。
@EnableTransactionManagement
// @org.mybatis.spring.annotation.MapperScanを付与し、Mapperインターフェースのスキャンを有効にします。
@MapperScan("com.example.todo.mapper")
public class MyBatisConfig {
// データソースのBean定義をします。
@Bean
public DataSource dataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://localhost:3306/DBの名前")
.username("設定した名前")
.password("設定したパスワード")
.build();
}
// org.mybatis.spring.SqlSessionFactoryBeanをBean定義します。
// これによりSqlSessionFactoryBeanを利用してSqlSessionFactoryが生成されます。
@Bean
public SqlSessionFactoryBean sqlSessionFactory() {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
// データソースを設定する。MyBatisの処理の中でSQLを発行すると、
// ここで指定したデータソースからコネクションが取得されます。
sessionFactoryBean.setDataSource(dataSource());
// MyBatis設定ファイルを指定します。
// 今回はresources直下に設定ファイルを配置します。
sessionFactoryBean.setConfigLocation(new ClassPathResource("/myBatis-config.xml"));
return sessionFactoryBean;
}
// トランザクションマネージャーのBeanを定義します。
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
src/main/resourcesの下に「myBatis-config.xml」を作成します
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="jdbcTypeForNull" value="NULL"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<package name="com.example.todo"/>
</typeAliases>
</configuration>
LocalDateの設定を行う
こちらを行わないと、DBでは日付の値を持っているにも関わらず、アプリケーション側ではnullとなるため、はまりました。
package com.example.todo;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
public class LocalDateTypeHandler extends BaseTypeHandler<LocalDate> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, LocalDate parameter, JdbcType jdbcType) throws SQLException {
ps.setDate(i, java.sql.Date.valueOf(parameter));
}
@Override
public LocalDate getNullableResult(ResultSet rs, String columnName) throws SQLException {
java.sql.Date date = rs.getDate(columnName);
return date != null ? date.toLocalDate() : null;
}
@Override
public LocalDate getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
java.sql.Date date = rs.getDate(columnIndex);
return date != null ? date.toLocalDate() : null;
}
@Override
public LocalDate getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
java.sql.Date date = cs.getDate(columnIndex);
return date != null ? date.toLocalDate() : null;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="jdbcTypeForNull" value="NULL"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<package name="com.example.todo"/>
</typeAliases>
<typeHandlers>
<typeHandler handler="com.example.todo.LocalDateTypeHandler"
javaType="java.time.LocalDate"/>
</typeHandlers>
</configuration>
Entityを作成する
src/main/java/com/todoの下に、「entity」パッケージを作成します。
app(com.todo)を右クリックし、「新規(W)」→「パッケージ」と進みます。
パッケージの下にTodo.javaクラスを作成します。
entityを右クリックし、「新規(W)」→「クラス」と進みます。
名前(M)に「Todo」と入力し、完了を押します。
package com.example.todo.entity;
import java.time.LocalDate;
import lombok.Data;
@Data
public class Todo {
private Integer id;
private String title;
private Integer done_flg;
private LocalDate time_limit;
}
※@Dataでsetter,getterを自動で作成している
Mapperを作成する
パッケージの下にTodoMapper.javaクラスを作成します。
mapperを右クリックし、「新規(W)→「インターフェース」と進みます。
名前(M)に「TodoMapper」と入力し、完了を押します。
TodoMapper.javaが作成されたので、編集していきます。
package com.example.todo.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.todo.entity.Todo;
@Mapper
public interface TodoMapper {
public List<Todo> selectAll();
}
XMLファイルの作成
Mapperインターフェースと同じ構成のフォルダを作成し、XMLファイルを作成していきます。
src/main/resourcesを右クリックし、
「新規(W)」→「フォルダー」と進みます。
フォルダー名に、「com/todo/app/mapper」と入力し、完了します。
mapperフォルダを右クリックし、
「新規(W)」→「その他」→「XMLファイル」と進みます。
ファイル名は、「TodoMapper.xml」とします。
TodoMapper.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.todo.mapper.TodoMapper">
<!-- resultMapの定義 -->
<resultMap id="todoResultMap" type="com.example.todo.entity.Todo">
<result property="id" column="id"/>
<result property="title" column="title"/>
<result property="done_flg" column="done_flg" />
<result property="time_limit" column="time_limit"
typeHandler="com.example.todo.LocalDateTypeHandler"/>
</resultMap>
<select id="selectAll" resultMap="todoResultMap">
SELECT * FROM todo_items
</select>
</mapper>
Thymeleafの作成
フロント部分を作っていきます。
src/main/resources/templatesの下に、index.htmlを作成します。
index.htmlが作成できたら、編集していきます。
<!DOCTYPE html>
<htmlxmlns:th="http://www.thymeleaf.org">
<head>
<metacharset="UTF-8">
<title>Todo</title>
</head>
<body>
<h1>Todo-List</h1>
<p th:each="todo : ${todos}"th:text="${todo.title}" />
</body>
</html>
Controllerの作成
src/main/java/com/todo/appの下に、「controller」パッケージを作成します。
controllerパッケージの下に、「TodoController.java」を作成します。
TodoController.javaが作成できたら、編集していきます。
package com.example.todo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import com.example.todo.entity.Todo;
import com.example.todo.mapper.TodoMapper;
@Controller
public class TodoController {
@Autowired
TodoMapper todoMapper;
@GetMapping("/")
public String index(Model model) {
List<Todo> list = todoMapper.selectAll();
model.addAttribute("todos", list);
return "index";
}
}
CRUD処理を実装する
ここからは、完成したコードを載せます。基礎的な知識があれば処理を追う事は可能だと思うので、是非追ってみてください。
package com.example.todo.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.todo.entity.Todo;
@Mapper
public interface TodoMapper {
public List<Todo> selectAll();
public List<Todo> selectIncomplete();
public List<Todo> selectComplete();
public void add(Todo todo);
public void update(Todo todo);
public void delete();
}
<?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.todo.mapper.TodoMapper">
<!-- resultMapの定義 -->
<resultMap id="todoResultMap" type="com.example.todo.entity.Todo">
<result property="id" column="id"/>
<result property="title" column="title"/>
<result property="done_flg" column="done_flg" />
<result property="time_limit" column="time_limit" typeHandler="com.example.todo.LocalDateTypeHandler"/>
</resultMap>
<select id="selectAll" resultMap="todoResultMap">
SELECT * FROM todo_items
</select>
<select id="selectIncomplete" resultMap="todoResultMap">
SELECT * FROM todo_items WHERE done_flg = 0
</select>
<select id="selectComplete" resultMap="todoResultMap">
SELECT * FROM todo_items WHERE done_flg = 1
</select>
<insert id="add" parameterType="com.example.todo.entity.Todo">
INSERT INTO todo_items (title, time_limit)
VALUES (#{title}, #{time_limit})
</insert>
<update id="update" parameterType="com.example.todo.entity.Todo">
UPDATE todo_items
SET title = #{title}, time_limit = #{time_limit}, done_flg = #{done_flg}
WHERE id = #{id}
</update>
<delete id="delete" parameterType="com.example.todo.entity.Todo">
DELETE FROM todo_items WHERE done_flg = 1
</delete>
</mapper>
package com.example.todo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import com.example.todo.entity.Todo;
import com.example.todo.mapper.TodoMapper;
@Controller
public class TodoController {
@Autowired
TodoMapper todoMapper;
@GetMapping("/")
public String index(Model model) {
// List<Todo> list = todoMapper.selectAll();
List<Todo> list = todoMapper.selectIncomplete();
List<Todo> doneList = todoMapper.selectComplete();
model.addAttribute("todos", list);
model.addAttribute("doneTodos", doneList);
return "index";
}
@PostMapping("/add")
public String add(Todo todo) {
todoMapper.add(todo);
return "redirect:/";
}
@PostMapping("/update")
public String update(Todo todo) {
todoMapper.update(todo);
return "redirect:/";
}
@PostMapping("/delete")
public String delete() {
todoMapper.delete();
return "redirect:/";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Todo</title>
</head>
<body>
<h1>Todo-List</h1>
<form method="post" th:action="@{/update}" th:each="todo : ${todos}">
<input type="checkbox" name="done_flg" value="1" th:checked="${todo.done_flg == 1}" />
<input type="hidden" name="done_flg" value="0" th:if="${todo.done_flg == 0}" />
<input type="hidden" name="id" th:value="${todo.id}" />
<input type="text" name="title" th:value="${todo.title}" />
<input type="date" name="time_limit" th:value="${todo.time_limit}" />
<input type="submit" value="更新" />
</form>
<h3>完了済み</h3>
<form method="post" th:action="@{/update}" th:each="todo : ${doneTodos}">
<input type="checkbox" name="done_flg" value="1" />
<input type="hidden" name="id" th:value="${todo.id}" />
<input type="text" name="title" th:value="${todo.title}" style="text-decoration:line-through"/>
<input type="date" name="time_limit" th:value="${todo.time_limit}" />
<input type="submit" value="更新" />
</form>
<h3>新しいタスクを追加</h3>
<form method="post" th:action="@{/add}">
<input type="text" name="title" />
<input type="date" name="time_limit" />
<input type="submit" value="追加" />
</form>
<form method="post" th:action="@{/delete}">
<input type="submit" value="完了済みを削除" />
</form>
</body>
</html>
おわりに
説明をあまり書いてませんが、キータの記事が丁寧に書いてあるので、参考いただければと思います。このアプリを土台として、改良して勉強したいと考えています。
お読みいただきありがとうございました。
コメント