0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JAX-RS(Jersey)で作るRESTful API①

Last updated at Posted at 2025-01-04

やりたいこと

JakartaEEのAPIを使ってRESTful APIを開発したい。

前提

【機能要件】
・データを全件JSON形式で取得できるGETメソッドのREST API
・データを1件指定してJSON形式で取得できるPOSTメソッドのREST API

【主な使用技術】
・Java17
・Apache Maven
・Apache Tomcat10
・PostgreSQL

【実装方式】
・プレゼンテーション層はJAX-RS(Jersey)
・ビジネスロジック層はCDI
・インテグレーション層はMyBatis
・JAX-RSランタイム中で発生したExceptionは適宜処理(ExceptionMapper)
・リクエスト到達時にユーザー名/パスワードで認証処理(ContainerRequestFilter)
・レスポンス返却時にログを記録(ContainerResponseFilter)

以上を図式化したものが下記です。
JerseySample-全体.jpg

①でやること

下記の赤枠で囲んである、REST APIアプリケーションの中心部(プレゼンテーション層、ビジネスロジック層、インテグレーション層)を取り扱います。
(例外処理、認証処理、ログはJAX-RS(Jersey)で作るRESTful API②で取り扱います。)
JerseySample-記事①.jpg

手順

①pom.xml/web.xml/beans.xml作成
②Dto/Dao作成
③Service作成
④Application/Resource作成

①pom.xml/web.xml/beans.xml作成

【pom.xml作成】
まずはpom.xmlを作成します。
下記依存をdependenciesに追加します。

		<!-- JAX-RS API -->
		<dependency>
			<groupId>jakarta.ws.rs</groupId>
			<artifactId>jakarta.ws.rs-api</artifactId>
			<version>4.0.0</version>
		</dependency>

		<!-- CDI API -->
		<dependency>
			<groupId>jakarta.enterprise</groupId>
			<artifactId>jakarta.enterprise.cdi-api</artifactId>
			<version>4.1.0</version>
		</dependency>

		<!-- MyBatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.17</version>
		</dependency>

		<!-- MyBatis-CDI -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-cdi</artifactId>
			<version>2.1.0</version>
		</dependency>

		<!-- Lombok -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.36</version>
			<scope>provided</scope>
		</dependency>

		<!-- PostgreSQL Driver -->
		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<version>42.7.4</version>
		</dependency>

		<!--Weld Servlet Core-->
		<dependency>
			<groupId>org.jboss.weld.servlet</groupId>
			<artifactId>weld-servlet-core</artifactId>
			<version>6.0.0.CR2</version>
		</dependency>

		<!--Jersey Core Server-->
		<dependency>
			<groupId>org.glassfish.jersey.core</groupId>
			<artifactId>jersey-server</artifactId>
			<version>4.0.0-M1</version>
		</dependency>

		<!--Jersey Container Servlet-->
		<dependency>
			<groupId>org.glassfish.jersey.containers</groupId>
			<artifactId>jersey-container-servlet</artifactId>
			<version>4.0.0-M1</version>
		</dependency>

		<!--Jersey Inject HK2 -->
		<dependency>
			<groupId>org.glassfish.jersey.inject</groupId>
			<artifactId>jersey-hk2</artifactId>
			<version>4.0.0-M1</version>
		</dependency>

		<!--Jersey Ext Cdi1x -->
		<dependency>
			<groupId>org.glassfish.jersey.ext.cdi</groupId>
			<artifactId>jersey-cdi1x</artifactId>
			<version>4.0.0-M1</version>
		</dependency>

		<!--Jersey Media JSON Jackson-->
		<dependency>
			<groupId>org.glassfish.jersey.media</groupId>
			<artifactId>jersey-media-json-jackson</artifactId>
			<version>4.0.0-M1</version>
		</dependency>

		<!--Jackson Databind-->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.18.2</version>
		</dependency>

		<!--org.slf4j-->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>2.0.16</version>
		</dependency>

		<!--logback-classic-->
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.5.15</version>
		</dependency>

内容は、JAX-RS、CDI、MyBatis関連、Lombok、PostgreSQL Driver、Weld Servlet、Jersey関連、Jackason、slf4j/logback等ログ関連です。
必要に応じてテストフレームワークやAPI Client フレームワークを追加します。

【web.xml作成】
src/main/webapp/WEB-INF配下にweb.xmlを作成します。
今回はweb.xmlに記述する内容は特にないので、下記のように特に中身は記述しません。

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0" metadata-complete="true">
    
</web-app>

【beans.xml作成】
次に、src/main/webapp/WEB-INF配下(web.xmlと同じディレクトリ)にbeans.xmlを作成します。
JavaEE7以降はbeans.xmlを省略してもCDIが有効化されるようですが、省略するとなぜか動かなかったので一応追加いたします。

<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
	version="1.1" bean-discovery-mode="all">
</beans>

②Dto/Dao作成

【テーブル定義】
Dto/Daoを作成するにあたり、テーブル定義を確認します。
下記が今回使用するtestdbの定義です。
image.png

【Dto作成】
上記テーブル定義に合わせてDtoを作成します。

package com.example.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class SampleDto {
	
	private int id;
	private String msg;

}

【Dao作成】
次にDaoを作成します。
今回はMyBatisを使用するため、Mapperを作成します。

package com.example.dao;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import com.example.dto.SampleDto;

@Mapper
public interface SampleMapper {
	
	List<SampleDto> getList();
	
	SampleDto getById(@Param("id") int id);

}

次に実際にSQLを記述するmapper.xmlを作成します。
(今回はsrc/mai/resources配下に作成します。)
クエリの内容は、今回はデータ全件取得とidを指定して1件だけ取得するものとします。

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.dao.SampleMapper">

	<select id="getList" parameterType="string" resultType="com.example.dto.SampleDto"> 
		SELECT 
		id, 
		msg 
		FROM 
		testdb.testdb;
	</select>
	
	<select id="getById" parameterType="int" resultType="com.example.dto.SampleDto"> 
		SELECT 
		id, 
		msg 
		FROM 
		testdb.testdb
		WHERE
		id = #{id};
	</select>
		
</mapper>

次に、MapperをCDI経由でインジェクションするためのmybatis-config.xmlとSqlSessionFactoryProducerクラスを作成します。
詳しくは、下記記事をご参照くださいませ。

mybatis-config.xml(src/mai/resources配下)

<?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>
	<environments default="test">
		<environment id="test">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="org.postgresql.Driver" />
				<property name="url" value="" />
				<property name="username" value="" />
				<property name="password" value="" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="./mapper.xml" />
	</mappers>
</configuration>

SqlSessionFactoryProducer.java

package com.example.util;

import java.io.IOException;
import java.io.InputStream;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.Produces;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.mybatis.cdi.SessionFactoryProvider;

@Dependent
public class SqlSessionFactoryProducer {
	
	@Produces
	@ApplicationScoped
	@SessionFactoryProvider
	public SqlSessionFactory produceFactory() throws IOException{
		InputStream fileStream = Resources.getResourceAsStream("mybatis-config.xml");
		return new SqlSessionFactoryBuilder().build(fileStream);
	}

}

③Service作成

次に、Serviceクラスを作成します。
今回は特にビジネスロジック層で必要な処理はないため、インテグレーション層からデータを取得しプレゼンテーション層にデータを横流しするのみとします。
ただし、MapperをSqlSession経由で取得する点に留意してください。
詳しくは、下記記事をご参照くださいませ。

SampleService.java

package com.example.service;

import java.util.List;

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;

import org.apache.ibatis.session.SqlSession;

import com.example.dao.SampleMapper;
import com.example.dto.SampleDto;

@RequestScoped
public class SampleService {
	
	@Inject
	private SqlSession sqlSession;
	
	
	public List<SampleDto> getList(){
		 return sampleMapper().getList();
	}
	
	public SampleDto getById(int id) {
		return sampleMapper().getById(id);
	}
	
	private SampleMapper sampleMapper() {
		return sqlSession.getMapper(SampleMapper.class);
	}

}

④Application/Resource作成

【Aplicationサブクラス作成】
まずはjakarta.ws.rs.core.Applicationを継承したサブクラスを作成します。
ポイントは2つ。
1つ目は、jakarta.ws.rs.core.Applicationを継承すること。
2つ目は、ApplicationPathを指定することです。
これにより、ApplicatioPath以下にアクセスした際、Resource等JAX-RSで使用するファイルをJAX-RSランタイムに通知することができます。
(web.xmlに記述する方法でもOKです。)

ApplicationConfig.java

package com.example.application;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("rest")
public class ApplicationConfig extends Application {

}

【リクエストボディのデータを格納するクラス作成】
今回、POST形式のAPIはリクエストボディからデータを取得しJavaクラスにバインドする仕様で作成するため、リクエストボディのデータを格納するクラスを作成します。

package com.example.model;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Input {
	
	private int id;

}

【Resource作成】
最後にResourceクラスを作成します。
以下が今回作成したResourceクラスの全体です。

package com.example.resource;

import java.util.List;

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import com.example.dto.SampleDto;
import com.example.exception.DataNotFoundException;
import com.example.model.Input;
import com.example.service.SampleService;

@Path("api")
@RequestScoped
public class SampleResource {
	
	@Inject
	private SampleService service;	
	
	
	@Path("getlist")
	@Produces(MediaType.APPLICATION_JSON)
	@GET
	public List<SampleDto> getList(){
		return service.getList();
	}
	
	@Path("getbyid")
	@Produces(MediaType.APPLICATION_JSON)
	@Consumes(MediaType.APPLICATION_JSON)
	@POST
	public SampleDto getById(Input input) {
		SampleDto dto = service.getById(input.getId());
		if(dto == null) {
			throw new DataNotFoundException();
		}else {
			return dto;
		}
	}

}

まずはクラスに付与しているアノテーションに関して。

@Path("api")
@RequestScoped
public class SampleResource {

本クラスで提供するリソースへアクセスするためのパスは、ApplicationPathと合わせてrest/api~となります。
また、CDIのスコープはリクエストスコープとして定義します。

次に、データ全件取得をGETで提供するメソッドに関して。

	@Path("getlist")
	@Produces(MediaType.APPLICATION_JSON)
	@GET
	public List<SampleDto> getList(){
		return service.getList();
	}

@GETによりGETメソッドで実行することができます。
また@Pathにより、rest/api/getlistで本リソースにアクセスすることができます。
また、@ProducesをJSONで定義することにより、レスポンス形式をJSON指定しています。

次に、データ1件取得をPOSTで提供するメソッドに関して。

	@Path("getbyid")
	@Produces(MediaType.APPLICATION_JSON)
	@Consumes(MediaType.APPLICATION_JSON)
	@POST
	public SampleDto getById(Input input) {
		SampleDto dto = service.getById(input.getId());
		if(dto == null) {
			throw new DataNotFoundException();
		}else {
			return dto;
		}
	}

@POSTによりPOSTメソッドで実行することができます。
また@Pathにより、rest/api/getbyidで本リソースにアクセスすることができます。
また、@ConsumesをJSONで定義することにより、JSON形式のリクエストボディからデータを取得し、引数のInput.javaにバインドすることができます。(SpringBootの@RequestBodyのような挙動になります。)
さらに、@ProducesをJSONで定義することにより、レスポンス形式をJSON指定しています。

実行

以上の作業ののち、作成したアプリケーションをビルドし、APサーバー(今回はTomcat10)にデプロイしてサーバーを起動します。
その後、URIを叩き、想定通りのレスポンスが返ってくれば完了です。
(今回作成したAPIのGETメソッドの場合、http://FQDN/コンテキストパス/rest/api/getlist)

最後に

ここまでで、APIの基本的な機能を実装することができました。
次回は例外処理、認証処理、ログ機能を実装します。
続きはこちらから↓↓↓

ソースコード

上記サンプルコードはGithubにコミットしています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?