Movatterモバイル変換


[0]ホーム

URL:


水まんじゅう2

Jakarta Persistence 3.2(Jakarta EE 11)からCalendar/Date/Timeなどが非推奨化されます!

これはJakarta EE / Java EE Advent Calendar 2025の7日目の記事です。

昨日は@kazumura によるJava系各種Landscape #AI - Qiitaで、
明日はQuarkusの記事です。(雑)

そろそろ上にあるリンクを編集するのも面倒くさくなってきましたが、皆様いかがお過ごしでしょうか?

Jakarta EE 11仕様のJPAでは大きな機能が非推奨化されました。

なんと、昔ながらの日付APIが非推奨化されました。

Deprecates usage of Calendar, Date, Time, Timestamp, Temporal, MapKeyTemporal and TemporalType in new applications in favour of java.time API

今後は、Java 8から導入されたDate and Time APIを使用する事が推奨されます。LocalDateなどです。なお、JPAではJava EE 8の頃からDate and TimeAPIをサポートしていますので、すでに使われている方も多いのではないかと思います。

古い日付APIが使われなくなると、java.util.Dateとjava.sql.Dateとjava.sql.Timestampの微妙な挙動の差に悩まされたりしなくなるのですが、多くのアプリケーションはいまだに古い日付APIを使い続けてると思いますので、対応を考える必要が出てきそうですね。

ただ、現実問題としては仕様としては将来的に削除されたとしても実装側では残り続けそうな気はします。さすがに今の使われ方を考えると消せない気がする・・・・・

ということで、簡単ですがここまでで。

Jakarta Persistence 3.2(Jakarta EE 11)で増えたJPQLの構文

これはJakarta EE / Java EE Advent Calendar 2025の5日目の記事です。

昨日は@megascus によるJBoss EAP 8に環境変数を使って値を設定する/7以前と挙動が変わっているので注意 - 赤帽エンジニアブログで、明日は@kazumura による記事です。

Jakarta EE 11ではJakarta Data が追加されたことが大きく取り上げられていますが、実は、そちらのベースとなる機能であるJakarta Persistenceの方も多数の機能が追加されています。

この記事では、Jakarta Persistence 3.2で増えたJPQLの構文を紹介します。

複数のSELECT文の結合

JPQLでは複数のSELECT文を結合する構文が追加されました。次のキーワードが使用できるようになっています。

UNION, UNION ALL, INTERSECT, INTERSECT ALL, EXCEPT, EXCEPT ALL

完全なシンタックスは次のように定義されています。

select_statement ::= unionunion ::= intersection | union {UNION [ALL] | EXCEPT [ALL]} intersectionintersection ::= query_expression | intersection INTERSECT [ALL] query_expressionquery_expression ::= select_query | (union)

これらの意味はSQLで使われているキーワードと全く同じです。

文字列の結合

文字列の結合に || が使えるようになりました。CONCATを書かなくてもよくなります。

idおよびversionファンクションの追加

エンティティのIDおよびエンティティのバージョンを返すファンクションが追加されました。

次のように使用できます。

DELETEfrom EmployeeWHERE id(this) = :idAND version(this) = :version

エンティティではIDやversion付けのための属性については、@Idや@Versionというアノテーションを付けて管理をします。それらについて、今まではアノテーションが付いているにもかかわらず、JPQLで直接指定する必要がありました。それが直接指定しなくても良くなったことになります。

castファンクションの追加

文字列と数値を切り替えるcastファンクションが追加されました。構文は次のようになります。

string_cast_function::=    CAST(scalar_expression AS STRING)arithmetic_cast_function::=    CAST(string_expression AS {INTEGER | LONG | FLOAT | DOUBLE})

ただし、キャストの結果がどうなるかについてはデータベースに依存するようです。

文字列操作のためのleft、right、replaceファンクションの追加

left、rightは1番目の文字列から2番目の引数の数値分の部分文字列を返し、replaceは1番目の文字列から2番目の文字列を検索し、3番目の文字列に置き換えます。

order byの改善

order byでnullを先に表示するか、後に表示するかが指定できるようになりました。また、order byにスカラー式を指定できるようになりました。

新しいシンタックスは次のようになります。

orderby_clause ::= ORDER BY orderby_item {, orderby_item}* orderby_item ::= orderby_expression [ASC | DESC] [NULLS {FIRST | LAST}] orderby_expression ::=     state_field_path_expression |     general_identification_variable |     result_variable |     scalar_expression

まとめ

ということで、簡単にJPQLで増えた構文について説明しました。特に、UNION系が増えたことは嬉しい人は非常に嬉しいんじゃないでしょうか。

JPQLで説明しましたが、これらについてはCriteriaQueryでも対応するメソッドが追加されています。

Jakarta PersistenceはHibernateチームの尽力もあり、一番アクティブに更新されている機能ですので、ぜひお試しください。

jakarta.ee

jakarta.ee

Jakarta EE 11(Servlet 6.1)で増えてたHttpSession.Accessorを使ってみる

Jakarta EE 11(Servlet 6.1)でHttpSessionに対する新しい制約が増えていました。

HttpSessionをHttpRequestのスコープ外から使用する事が禁止されました。そのため、外部から使用できるようにHttpSession.Accessorというインターフェースが増えています。

spring.pleiades.io

業務要件などにより特定の条件で一括してログアウトしたい場合とかのためにHttpSessionの一覧を取っておきたい場合があったのですが、GCとかで不都合があったんでしょうねぇ。。。

ということで、試してみようと思います。

使用したのはApacheTomcat/11.0.1 です。

Rootにアクセスしてセッションを作成するクラス。

import java.io.IOException;import jakarta.servlet.ServletException;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;@WebServlet(urlPatterns ="/")publicclass Rootextends HttpServlet {@Overrideprotectedvoid doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {        req.getSession(true);        resp.getWriter().write("accessed!");    }}

Listenerを作ってAccessorをキャッシュします。ついでに古い機能が動くかどうかを試すためにHttpSessionもキャッシュします。

import java.util.ArrayList;import java.util.Collections;import java.util.List;import jakarta.servlet.annotation.WebListener;import jakarta.servlet.http.HttpSession;import jakarta.servlet.http.HttpSession.Accessor;import jakarta.servlet.http.HttpSessionEvent;import jakarta.servlet.http.HttpSessionListener;@WebListenerpublicclass MySessionListenerimplements HttpSessionListener {publicstatic List<Accessor> accessors = Collections.synchronizedList(new ArrayList<>());//正しいやり方publicstatic List<HttpSession> sessions = Collections.synchronizedList(new ArrayList<>());//今後Invalidになる@Overridepublicvoid sessionCreated(HttpSessionEvent se) {        accessors.add(se.getSession().getAccessor());        sessions.add(se.getSession());    }//sessionDestroyedでListから取り除くのは省略}

/listには一覧をループしてIDを表示するようなプログラムを書いてます。

import java.io.IOException;import java.io.PrintWriter;import jakarta.servlet.ServletException;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;@WebServlet(urlPatterns ="/list")publicclass GetSessionListextends HttpServlet {@Overrideprotectedvoid doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {final PrintWriter writer = resp.getWriter();        writer.write("Accessor:");        MySessionListener.accessors.forEach(t -> t.access(s -> writer.write(s.getId()+"\r\n")));        writer.write("HttpSession:");        MySessionListener.sessions.forEach(s -> writer.write(s.getId()+"\r\n"));    }}

適当にcurlを使って数回リクエストを投げます。

curl http://localhost:8080/curl http://localhost:8080/curl http://localhost:8080/curl http://localhost:8080/

で、/listにアクセスします。

Accessor:A3B1369126D75435EEE5FB975B91C5C7F9EC68EC8447FF7EE0948561B867ABBC5C74C432AB5199D634F282D149C5A5FD2F4E054EDF06AF15DC43E6132A8F74C9HttpSession:A3B1369126D75435EEE5FB975B91C5C7F9EC68EC8447FF7EE0948561B867ABBC5C74C432AB5199D634F282D149C5A5FD2F4E054EDF06AF15DC43E6132A8F74C9

同じセッションオブジェクトにアクセスできてそうです。

また、現時点のTomcatでは、少なくともSessionが生きている段階において、HttpSessionを直接キャッシュして使用しても即座にエラーになることはなさそうです。

ただ、このやり方でAccessorを経由してHttpSessionにアクセスしてSessionを無効化してもHttpServletRequest#logout()は呼び出せていないため、SSOを使用している場合でのログアウトには問題が発生することがあります。また、仕組み的にHttpSessionからHttpServletRequestオブジェクトにアクセスすることは出来ません。IDPの方での手当を考えないとですかねぇ。

最新のLinuxと古めのLinuxが混在している環境での/dev/randomを使っての乱数生成に注意

Quarkus 3.13.3 released - Maintenance release というのがありました。

quarkus.io

これって何が原因かというと、使用しているライブラリの乱数生成にLinux/dev/random にアクセスして乱数を取得するものがあったことです。

Linux/dev/random はデバイスの入力などをもとに完全な乱数を生成することができるものでした。問題点として、デバイスの入力などを基に乱数を生成するので、デバイスからの入力が十分に無い場合、例えばサーバー用途でユーザーからの入力が行われない場合は乱数の生成ができずにブロックされてしまいます。つまり、/dev/random にアクセスしても応答が返ってきません。

古くからこの問題は良く知られており、よく知られた解決策としては/dev/urandom を使用する事です。

Java でもFAQレベルで多くの記事が見つかります。

www.google.com

/dev/random について詳しく知りたい人は以下の記事でも。

utakamo.com

で、最近Linux kernelの5系の途中からこの/dev/random がブロックしなくなりました。RHELだと8まではブロックして、9からはブロックしなくなります。

access.redhat.com

で、何が発生するかというと、RHEL 9や他の最新のLinuxでは動いたものが8以前、もしくは他のLinuxのちょっと古い環境では動かなくなる可能性があるという事です。

まとめ

当たった人南無。

Quarkusのテストでこれが見つからなかった原因かどうかは知らないけどね。

WildFly-27.0.0.1.Beta1とKotlinでJakarta EE10を試す

ひとつ前の記事WildFly-27.0.0.1.Beta1を試してみたら簡単に動いてしまったのでKotlinでも試してみました。

WildFly-27.0.0.1.Beta1が立ち上がってる状態で、pom.xmlを書き換えます。

基本的にKotlinの公式サイトに載っているものをそのまま追加しています。(maven-compiler-pluginが古かったのだけ更新)

https://kotlinlang.org/docs/maven.html

<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>dev.megascus</groupId><artifactId>jakartaee10</artifactId><version>0.0.1-SNAPSHOT</version><packaging>war</packaging><name>jakartaee10</name><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.release>11</maven.compiler.release><kotlin.compiler.incremental>true</kotlin.compiler.incremental><kotlin.version>1.7.20</kotlin.version></properties><dependencies><dependency><groupId>jakarta.platform</groupId><artifactId>jakarta.jakartaee-api</artifactId><version>10.0.0</version></dependency><dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib</artifactId><version>${kotlin.version}</version></dependency></dependencies><build><finalName>${project.artifactId}</finalName><plugins><plugin><artifactId>maven-war-plugin</artifactId><version>3.3.2</version><configuration><failOnMissingWebXml>false</failOnMissingWebXml></configuration></plugin><plugin><groupId>org.wildfly.plugins</groupId><artifactId>wildfly-maven-plugin</artifactId><version>3.0.0.Final</version></plugin><plugin><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-maven-plugin</artifactId><version>${kotlin.version}</version><executions><execution><id>compile</id><goals><goal>compile</goal></goals><configuration><sourceDirs><sourceDir>${project.basedir}/src/main/kotlin</sourceDir><sourceDir>${project.basedir}/src/main/java</sourceDir></sourceDirs></configuration></execution><execution><id>test-compile</id><goals><goal>test-compile</goal></goals><configuration><sourceDirs><sourceDir>${project.basedir}/src/test/kotlin</sourceDir><sourceDir>${project.basedir}/src/test/java</sourceDir></sourceDirs></configuration></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.9.0</version><executions><!-- Replacing default-compile as it is treated specially by maven --><execution><id>default-compile</id><phase>none</phase></execution><!-- Replacing default-testCompile as it is treated specially by maven --><execution><id>default-testCompile</id><phase>none</phase></execution><execution><id>java-compile</id><phase>compile</phase><goals><goal>compile</goal></goals></execution><execution><id>java-test-compile</id><phase>test-compile</phase><goals><goal>testCompile</goal></goals></execution></executions></plugin></plugins></build></project>

で、前回のTestServletをKotlinで書きなおします。書き直してsrc/main/kotlinに移して、古いものを削除しています。

import java.io.IOException;import jakarta.servlet.ServletException;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.Cookie;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;@WebServlet(name ="test", urlPatterns = arrayOf("/*"))class HelloWorld : HttpServlet() {@Overridepublicoverridefun service(req: HttpServletRequest, res: HttpServletResponse) {val cookie = Cookie("TEST","VALUE")        cookie.setAttribute("Max-Age","999")        res.addCookie(cookie)        res.getWriter().write("hello world from kotlin!")    }}

最後にmvn wildfly:deploy でデプロイすると普通にアクセスできるようになりました。

WildFly-27.0.0.1.Beta1でJakarta EE10を試す

WildFlyもαからβに無事になり、そこそこ安定してそうなので試してみました。思っていたよりはすんなり動いてよかったです。

WildFlyの準備

公式サイトにリンクが張られているので、ダウンロードします。

www.wildfly.org

ダウンロードしたら適当な場所に解凍します。

実行する場合はbinの中に入っているstandalone.bat/standalone.ps1/standalone.shをOSに合わせて実行するのが一番早いです。

Javaが入っていない場合は先にJavaをダウンロードしてインストールしている必要があります。

※この記事では、Windows上でjava-11-openjdk-11.0.16-2を使用しています。あと、mavenも。

起動すると以下のようにログが流れて起動します。

Calling "C:\apps\wildfly-27.0.0.Beta1\bin\standalone.conf.bat"Setting JAVA property to "C:\Program Files\RedHat\java-11-openjdk-11.0.16-2\bin\java"===============================================================================  JBoss Bootstrap Environment  JBOSS_HOME: "C:\apps\wildfly-27.0.0.Beta1"  JAVA: "C:\Program Files\RedHat\java-11-openjdk-11.0.16-2\bin\java"  JAVA_OPTS: "-server -Dprogram.name=standalone.bat -Xms64M -Xmx512M -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true  --add-exports=java.desktop/sun.awt=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.url.ldap=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.url.ldaps=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED "===============================================================================19:38:37,719 INFO  [org.jboss.modules] (main) JBoss Modules version 2.0.3.Final19:38:38,206 INFO  [org.jboss.msc] (main) JBoss MSC version 1.4.13.Final19:38:38,211 INFO  [org.jboss.threads] (main) JBoss Threads version 2.4.0.Final(中略)19:38:40,595 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Full 27.0.0.Beta1 (WildFly Core 19.0.0.Beta18) started in 3107ms - Started 290 of 563 services (357 services are lazy, passive or on-demand) - Server configuration file in use: standalone.xml19:38:40,596 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management19:38:40,597 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990

デフォルトでローカルホストの8080で立ち上がるのでアクセスしてみます。

http://localhost:8080/

実際にアプリを作ってみる

pom.xml

Jakarta EE 10のdependencyがあるので、それを使います。それ以外はwildfly-maven-pluginが入っている以外は普通のJakarta EE(Java EE)の最小限のpom.xmlです。

<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>dev.megascus</groupId><artifactId>jakartaee10</artifactId><version>0.0.1-SNAPSHOT</version><packaging>war</packaging><name>jakartaee10</name><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.release>11</maven.compiler.release></properties><dependencies><dependency><groupId>jakarta.platform</groupId><artifactId>jakarta.jakartaee-api</artifactId><version>10.0.0</version></dependency></dependencies><build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.9.0</version></plugin><plugin><artifactId>maven-war-plugin</artifactId><version>3.3.2</version><configuration><failOnMissingWebXml>false</failOnMissingWebXml></configuration></plugin><plugin><groupId>org.wildfly.plugins</groupId><artifactId>wildfly-maven-plugin</artifactId><version>3.0.0.Final</version></plugin></plugins></build></project>

Servlet

最低限のサーブレットと、Jakarta EE 10で新しく生えたAPIを使いたかったので、Cookie#setAttributeを呼び出してみています。

ネームスペースがjavaxからjakartaに変更されているのに注意してください。

package dev.megascus.jakartaee10;import java.io.IOException;import jakarta.servlet.ServletException;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.Cookie;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;@WebServlet(name ="test", urlPatterns ="/*")publicclass TestServletextends HttpServlet {@Overridepublicvoid service(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {        Cookie cookie =new Cookie("TEST","VALUE");        cookie.setAttribute("Max-Age","999");        res.addCookie(cookie);        res.getWriter().write("hello world!");    }}

デプロイしてみる

デプロイは簡単で以下コマンドを打つだけです。

mvn wildfly:deploy

デプロイ先にアクセスしてみると確かにhello world!と表示されています。また、Cookieも追加されていました。

ということで、Jakarta EE 10がリリースされて、すでにベータ版のリリースは始まっています。早めに試してみてはいかがでしょうか。

検索

引用をストックしました

引用するにはまずログインしてください

引用をストックできませんでした。再度お試しください

限定公開記事のため引用できません。

読者です読者をやめる読者になる読者になる

[8]ページ先頭

©2009-2025 Movatter.jp