JavaFX是Java编程语言的一个图形用户界面工具包,它可以轻松创建丰富、现代化的用户界面。在实际开发中,有时候需要加载JavaFX图像并将其保存到数据库中。本文将为您介绍如何实现这一操作,通过简单易懂的步骤,帮助您解决这一问题。让我们一起来探讨吧!
我现在正在努力将一个小图像保存在数据库中并将其加载以用作 javafx.scene.image.Image。我尝试过的几个解决方案涉及使用 SwingFXUtils.fromFXImage 和 SwingFXUtils.toFxImage,但该类似乎需要 module-info.java 文件中的一个条目,而我什至不打算使用该条目。
我在尝试使用 SwingFXUtils 时遇到此异常:java.lang.NoClassDefFoundError: javafx/embed/swing/SwingFXUtils
。
当我使用模块系统时,它破坏了我正在使用的另一个库(itextpdf)。 所以我想避免这种情况,只是找到另一种方法来保存和加载数据库中的 JavaFX 图像。 有什么建议吗?
您提到:
这不是真的。尽管 javafx 仅支持作为命名模块加载1,但 javafx 并不要求您自己的代码是模块化的。获得 noclassdeffounderror
的唯一方法是在运行时未能将类包含在模块路径/类路径中(它必须在编译时存在,否则您的代码将无法编译)。我建议阅读 Getting Started with JavaFX,了解如何使用主要 java ide 和/或构建工具之一设置基本 javafx 应用程序。但是,如果不知道您如何配置项目以及如何运行项目,我们将无法告诉您您的设置具体出了什么问题。
也就是说,您想要完成的总体目标确实是可能的。下面是一个示例,允许您浏览计算机上的图像并将其保存到内存中的 h2 数据库中。图像的 id 和名称被放入 tableview
中,其中包含一个带有“打开”按钮的列,可让您查看从内存数据库加载的图像。图像以 png 格式作为 blob 存储在数据库中,无论其原始格式如何。 swingfxutils
和 imageio
类用于将 javafx 图像转换为 png“文件”2。
该示例未向您展示如何部署应用程序(例如,通过 jpackage
)。
以下是我用于构建和运行示例的库和工具的版本。
java 21.0.1(eclipse adoptium / temurin)
javafx 21.0.1
h2 2.2.224
gradle 8.4
JavaFX Gradle Plugin 0.1.0
在 comment 至 previous question2 中,您声明您正在使用 gradle,因此我在示例中使用的是 gradle。
没有 module-info.java
文件。
imagerecord.java
package com.example; public record imagerecord(int id, string name) {}
main.java
package com.example; import java.io.file; import java.util.concurrent.executor; import java.util.concurrent.executors; import java.util.function.consumer; import javafx.application.application; import javafx.beans.property.simpleintegerproperty; import javafx.beans.property.simpleobjectproperty; import javafx.beans.property.simplestringproperty; import javafx.concurrent.task; import javafx.geometry.insets; import javafx.geometry.pos; import javafx.scene.scene; import javafx.scene.control.button; import javafx.scene.control.scrollpane; import javafx.scene.control.tablecell; import javafx.scene.control.tablecolumn; import javafx.scene.control.tableview; import javafx.scene.image.image; import javafx.scene.image.imageview; import javafx.scene.layout.borderpane; import javafx.scene.layout.hbox; import javafx.stage.filechooser; import javafx.stage.stage; import javafx.stage.stagestyle; import javafx.stage.window; public class main extends application { private final executor executor = executors.newvirtualthreadpertaskexecutor(); private final imagesdatabase db = new imagesdatabase("test"); private final imagerepository imagerepo = new imagerepository(db); private file lastdirectory; @override public void start(stage primarystage) { var table = createtable(record -> displayimage(primarystage, record)); var choosebtn = new button("choose image..."); choosebtn.setonaction( e -> { e.consume(); var image = chooseimage(primarystage); if (image != null) { executor.execute(createsaveimagetask(image, table.getitems()::add)); } }); var root = new borderpane(); root.settop(choosebtn); root.setcenter(table); borderpane.setalignment(choosebtn, pos.center); borderpane.setmargin(choosebtn, new insets(10)); primarystage.setscene(new scene(root, 600, 400)); primarystage.show(); } @override public void stop() throws exception { db.close(); } private image chooseimage(window owner) { var chooser = new filechooser(); chooser.settitle("choose image file"); chooser .getextensionfilters() .add(new filechooser.extensionfilter("image files", "*.jpeg", "*.jpg", "*.png")); if (lastdirectory != null) { chooser.setinitialdirectory(lastdirectory); } var file = chooser.showopendialog(owner); if (file != null) { lastdirectory = file.getparentfile(); return new image(file.touri().tostring()); } return null; } private void displayimage(window owner, imagerecord record) { var view = new imageview(); var task = creategetimagetask(record, view::setimage); executor.execute(task); var sp = new scrollpane(view); sp.setpannable(true); var window = new stage(stagestyle.utility); window.initowner(owner); window.settitle(record.name()); window.setscene(new scene(sp, 500, 300)); window.setonhiding(e -> task.cancel()); window.show(); } private tableview<imagerecord> createtable(consumer<imagerecord> onopen) { var table = new tableview<imagerecord>(); table.setcolumnresizepolicy(tableview.constrained_resize_policy_flex_last_column); var idcol = new tablecolumn<imagerecord, number>("id"); idcol.setcellvaluefactory(data -> new simpleintegerproperty(data.getvalue().id())); table.getcolumns().add(idcol); var namecol = new tablecolumn<imagerecord, string>("name"); namecol.setcellvaluefactory(data -> new simplestringproperty(data.getvalue().name())); table.getcolumns().add(namecol); var openbtncol = new tablecolumn<imagerecord, imagerecord>(); openbtncol.setcellvaluefactory(data -> new simpleobjectproperty<>(data.getvalue())); openbtncol.setcellfactory(tc -> createopenbuttoncell(onopen)); table.getcolumns().add(openbtncol); return table; } private tablecell<imagerecord, imagerecord> createopenbuttoncell(consumer<imagerecord> onopen) { return new tablecell<>() { final hbox container = new hbox(); final button openbutton = new button("open"); { container.getchildren().add(openbutton); container.setalignment(pos.center); openbutton.setonaction( e -> { e.consume(); var item = isempty() ? null : getitem(); if (item != null) { onopen.accept(item); } }); } @override protected void updateitem(imagerecord item, boolean empty) { super.updateitem(item, empty); if (empty || item == null) { setgraphic(null); } else { setgraphic(container); } } }; } private task<?> createsaveimagetask(image image, consumer<imagerecord> onsuccess) { return new task<imagerecord>() { @override protected imagerecord call() throws exception { return imagerepo.insertimage(image); } @override protected void succeeded() { onsuccess.accept(getvalue()); } @override protected void failed() { getexception().printstacktrace(); } }; } private task<?> creategetimagetask(imagerecord record, consumer<image> onsuccess) { return new task<image>() { @override protected image call() throws exception { return imagerepo.getimage(record).orelsethrow(); } @override protected void succeeded() { onsuccess.accept(getvalue()); } @override protected void failed() { getexception().printstacktrace(); } }; } }
imagerepository.java
package com.example; import static java.sql.statement.return_generated_keys; import java.io.bytearrayinputstream; import java.io.bytearrayoutputstream; import java.io.ioexception; import java.io.inputstream; import java.io.uncheckedioexception; import java.sql.sqlexception; import java.util.arraylist; import java.util.list; import java.util.objects; import java.util.optional; import java.util.concurrent.atomic.atomicinteger; import javafx.embed.swing.swingfxutils; import javafx.scene.image.image; import javax.imageio.imageio; public class imagerepository { private static final string select_all_records_sql = "select id, name from images"; private static final string select_image_sql = "select image from images where id = ?"; private static final string insert_sql = "insert into images (name, image) values (?, ?)"; private final atomicinteger generatednamecount = new atomicinteger(); private final imagesdatabase db; public imagerepository(imagesdatabase db) { this.db = db; } public list<imagerecord> getrecords() throws sqlexception { return db.execute( conn -> { try (var stat = conn.createstatement()) { var result = stat.executequery(select_all_records_sql); var records = new arraylist<imagerecord>(); while (result.next()) { int id = result.getint(1); var name = result.getstring(2); records.add(new imagerecord(id, name)); } return records; } }); } public optional<image> getimage(imagerecord record) throws sqlexception { return getimage(record.id()); } public optional<image> getimage(int recordid) throws sqlexception { if (recordid <= 0) { throw new illegalargumentexception("recordid <= 0: " + recordid); } return db.execute( conn -> { try (var stat = conn.preparestatement(select_image_sql)) { stat.setint(1, recordid); var result = stat.executequery(); if (result.next()) { var image = new image(result.getbinarystream(1)); return optional.of(image); } else { return optional.empty(); } } }); } public imagerecord insertimage(image image) throws sqlexception { objects.requirenonnull(image); return db.execute( conn -> { try (var stat = conn.preparestatement(insert_sql, return_generated_keys)) { var name = getimagename(image); stat.setstring(1, name); stat.setbinarystream(2, imagetoinputstream(image)); stat.executeupdate(); var keys = stat.getgeneratedkeys(); if (keys.next()) { int id = keys.getint(1); return new imagerecord(id, name); } else { throw new illegalstateexception("generated key not returned"); } } }); } private string getimagename(image image) { var source = image.geturl(); return source == null ? generateimagename() : source; } private string generateimagename() { return "generated image name " + generatednamecount.incrementandget(); } private inputstream imagetoinputstream(image image) { var out = new bytearrayoutputstream(); try { imageio.write(swingfxutils.fromfximage(image, null), "png", out); } catch (ioexception ex) { throw new uncheckedioexception(ex); } return new bytearrayinputstream(out.tobytearray()); } }
imagesdatabase.java
package com.example; import java.sql.connection; import java.sql.sqlexception; import java.util.objects; import java.util.concurrent.locks.lock; import java.util.concurrent.locks.reentrantlock; import javax.sql.datasource; import org.h2.jdbcx.jdbcdatasource; public class imagesdatabase implements autocloseable { private static final string create_table_sql = "create table images (id identity, name varchar(255), image blob)"; @functionalinterface public interface sqlfunction<t> { t execute(connection connection) throws sqlexception; } private final lock mutex = new reentrantlock(); private final datasource source; private connection connection; private boolean open = true; private boolean initialized; public imagesdatabase(string name) { if (name.isblank()) { throw new illegalargumentexception("blank name"); } var source = new jdbcdatasource(); source.seturl("jdbc:h2:mem:" + name + ";db_close_delay=-1"); this.source = source; } public <t> t execute(sqlfunction<t> function) throws sqlexception { objects.requirenonnull(function); mutex.lock(); try { checkopen(); return function.execute(getoropenconnection()); } finally { mutex.unlock(); } } private connection getoropenconnection() throws sqlexception { if (connection == null || connection.isclosed()) { connection = source.getconnection(); initialize(connection); } return connection; } private void initialize(connection conn) throws sqlexception { if (!initialized) { try (var stat = conn.createstatement()) { stat.executeupdate(create_table_sql); } initialized = true; } } private void shutdown() throws sqlexception { if (initialized) { try (var conn = getoropenconnection(); var stat = conn.createstatement()) { stat.execute("shutdown"); } connection = null; } } private void checkopen() { if (!open) { throw new illegalstateexception("closed"); } } @override public void close() throws sqlexception { mutex.lock(); try { if (open) { open = false; shutdown(); } } finally { mutex.unlock(); } } }
我使用了 kotlin dsl,但如果您愿意,您也可以使用 groovy dsl。
settings.gradle.kts
rootproject.name = "h2images-example"
build.gradle.kts
plugins { id("org.openjfx.javafxplugin") version "0.1.0" application } group = "com.example" version = "1.0" javafx { modules("javafx.controls", "javafx.swing") version = "21.0.1" } application { mainclass.set("com.example.main") } repositories { mavencentral() } dependencies { implementation("com.h2database:h2:2.2.224") }
您可以使用以下命令执行上述内容:
./gradlew run
注意 ./gradlew
调用 Gradle Wrapper。如果您的计算机上安装了 gradle 版本,则可以通过以下方式生成版本 8.4 的包装器:
gradle wrapper --gradle-version 8.4
1. javafx 在技术上不支持从类路径加载。这意味着理想情况下,javafx 模块应该位于模块路径上并解析为命名模块,即使您自己的代码和其他依赖项是从类路径加载的。但是,我不知道如果 javafx 位于类路径上(至少从 javafx 21 开始),会发生什么中断,除了您的主类不能再是 javafx.application 的子类。 application
(您需要一个单独的“启动器类”作为主类)。只需要知道,由于 javafx 位于类路径上而导致的任何问题都不太可能被 javafx 团队修复。
请注意,openjfx 提供的 gradle 和 maven 插件会配置这些构建工具以将 javafx 放在模块路径上。
2. 根据您的两个问题的上下文,该示例将 image
对象转换为 png 字节。但是,如果您已经以字节形式接收图像(即作为本地或远程文件),那么将这些字节直接放入数据库可能会更容易、更高效。
以上是加载 JavaFX 图像并将其保存到数据库的详细内容。更多信息请关注PHP中文网其他相关文章!