首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >JavaFX + Spring (JDBC & @SpringBootApplication & @Autowired & @Transactional)

JavaFX + Spring (JDBC & @SpringBootApplication & @Autowired & @Transactional)
EN

Stack Overflow用户
提问于 2017-01-09 14:32:56
回答 2查看 1.6K关注 0票数 2

我想在使用Spring的DB访问中使用JavaFX。然而,我对Spring完全陌生,而且我似乎无法完全理解它的特性,特别是处理.

我将下列依赖项添加到我的项目中:

代码语言:javascript
运行
复制
compile 'org.springframework.boot:spring-boot-starter-jdbc'
runtime 'mysql:mysql-connector-java'

..。在GUI应用程序对DB进行操作时,我希望使用Spring事务处理机制。据我所知,以下代码应该:

  • 初始化并启动JavaFX应用程序-创建并显示GUI线框
  • 初始化Spring
  • 配置和注入JdbcTemplate依赖项
  • 启动事务处理机制并开始事务处理
  • 使用jdbcTemplate对象在for loop中在DB中创建5个条目
  • 模拟错误(通过抛出RuntimeException)
  • 数据库上的还原操作
  • 出口

因此,总结一下:当RuntimeException被抛出并注释为@Transactional的方法中时,应该在应用程序退出之前恢复该方法已经创建的所有条目,不是吗?

然而,所有创建的条目都永久保留在DB中(我可以在应用程序退出后看到它们)。因此,首先-我是否正确地理解了这些事务应该如何工作?如果是这样的话,那么如何使它们像我所期望的那样工作呢?

代码语言:javascript
运行
复制
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;


@SpringBootApplication
public class SpringTransactional extends Application {
    private Pane viewPane;

    private ConfigurableApplicationContext springContext;

    /** application.properties:
     spring.datasource.driver-class-name = com.mysql.jdbc.Driver
     spring.datasource.url = jdbc:mysql://localhost:3306/db_name?useSSL=false&serverTimezone=UTC
     spring.datasource.username = db_username
     spring.datasource.password = username123
     */
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void init() throws Exception {
        springContext = SpringApplication.run(SpringTransactional.class);
        springContext.getAutowireCapableBeanFactory().autowireBean(this);
    }

    @Override
    public void stop() throws Exception {
        springContext.close();
    }

    @Override
    public void start(Stage primaryStage) {
        viewPane = assembleView(primaryStage);

        try {
            db_transaction_test();
        } catch (RuntimeException e) {
            e.printStackTrace();
        }

        Platform.exit();
    }

    private Pane assembleView(Stage primaryStage) {
        VBox rootPane = new VBox();
        rootPane.setSpacing(10);
        rootPane.setPadding(new Insets(10));
        rootPane.setStyle("-fx-base: #84a7ad;");
        rootPane.getChildren().add(new Label("GUI goes here."));

        primaryStage.setScene(new Scene(rootPane));
        primaryStage.setResizable(false);
        primaryStage.show();

        return rootPane;
    }

    @Transactional
    private void db_transaction_test() {
        for (int i = 0; i < 10; i++) {
            try {
                int entry_name = getEntryId("entry_" + i);
                System.out.println("Created entry id=" + entry_name);
            } catch (DaoException e) {
                e.printStackTrace();
            }

            if (i == 5) {
                throw new RuntimeException("Testing data upload procedure break.");
            }
        }
    }

    /** DB creation and schema:
     CREATE DATABASE db_name;
     CREATE USER db_username;

     USE db_name;
     GRANT ALL ON db_name.* TO db_username;

     SET PASSWORD FOR spz = PASSWORD('username123');
     FLUSH PRIVILEGES;

     CREATE TABLE Entry (
     entry_ID INT NOT NULL AUTO_INCREMENT,
     name   TEXT NOT NULL,

     PRIMARY KEY (entry_ID)
     );
     */
    private int getEntryId(String entryName) throws DaoException {
        List<DbEntry> dbEntries = retrieveEntriesFor(entryName);

        if (dbEntries.size() == 1) {
            return dbEntries.get(0).getEntry_ID();
        } else if (dbEntries.size() == 0) {
            String sqlInsert = "INSERT INTO Entry (name) VALUES (?)";
            jdbcTemplate.update(sqlInsert, entryName);
            dbEntries = retrieveEntriesFor(entryName);
            if (dbEntries.size() == 1) {
                return dbEntries.get(0).getEntry_ID();
            } else {
                throw new DaoException("Invalid results amount received after creating new (" + dbEntries.size() + ") when getting entry for name: " + entryName);
            }
        } else {
            throw new DaoException("Invalid results amount received (" + dbEntries.size() + ") when getting entry for name: " + entryName);
        }
    }

    private List<DbEntry> retrieveEntriesFor(String entryName) {
        return jdbcTemplate.query("SELECT * FROM Entry WHERE name=?;", (ResultSet result, int rowNum) -> unMarshal(result), entryName);
    }

    private DbEntry unMarshal(ResultSet result) throws SQLException {
        DbEntry dbEntry = new DbEntry();
        dbEntry.setEntry_ID(result.getInt("entry_ID"));
        dbEntry.setName(result.getString("name"));
        return dbEntry;
    }

    public class DbEntry {
        private int entry_ID;
        private String name;

        int getEntry_ID() { return entry_ID; }
        void setEntry_ID(int entry_ID) { this.entry_ID = entry_ID; }
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
    }

    private class DaoException extends Throwable {
        DaoException(String err_msg) { super(err_msg); }
    }
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-01-10 09:09:44

经过更多的测试后,创建单独的Spring EntryDao似乎是有效的(谢谢James_D),但前提是使用@Transactional注释的db_transaction_test在下面代码中的选项A中。

但我真正感兴趣的是选项B--当用@Transactional注释的@Transactional在另一个类中时。这是因为DAO类不知道(也不应该)了解DB --未实现的问题,这些问题是还原以前的几个DB操作的原因。此信息来自其他“控制器”,这些“控制器”故障不能导致数据完整性问题。因此,在下面的示例中,SpringTransactional应该是唯一能够抛出这个特定RuntimeException("Testing data upload procedure break.");RuntimeException("Testing data upload procedure break.");(作为现实系统/环境问题的一个例子)。但是,正如最终显示的堆栈跟踪一样--事务在那里并不是无效的。

那么,是否有一种方法可以让它像我需要的那样使用Spring@Transactional(又名Spring )。声明性事务)或仅使用手动(也称为。(编程) Spring事务控制?如果这是唯一的方法,那么如何配置DataSourceTransactionManager,同时使用@SpringBootApplication进行“自动配置”,使用@Autowired实现jdbcTemplate对象?

主修班:

代码语言:javascript
运行
复制
package tmp;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.transaction.annotation.Transactional;
import tmp.dao.EntryDao;


@SpringBootApplication
public class SpringTransactional extends Application {
    private Pane viewPane;

    private ConfigurableApplicationContext springContext;

    @Autowired
    private EntryDao dao;

    public static void main(String[] args) { launch(args); }

    @Override
    public void init() throws Exception {
        springContext = SpringApplication.run(SpringTransactional.class);
        springContext.getAutowireCapableBeanFactory().autowireBean(this);
    }

    @Override
    public void stop() throws Exception { springContext.close(); }

    @Override
    public void start(Stage primaryStage) {
        viewPane = assembleView(primaryStage);

        // OPTION A:
        try {
            dao.db_transaction_test();
        } catch (RuntimeException e) {
            e.printStackTrace();
        }

        // OPTION B:
        try {
            db_transaction_test();
        } catch (RuntimeException e) {
            e.printStackTrace();
        }

        Platform.exit();
    }

    @Transactional
    private void db_transaction_test() {
        for (int i = 0; i < 10; i++) {
            try {
                int entry_name = dao.getEntryId("entry_" + i);
                System.out.println("Created entry id=" + entry_name);
            } catch (EntryDao.DaoException e) {
                e.printStackTrace();
            }

            if (i == 5) {
                throw new RuntimeException("Testing data upload procedure break.");
            }
        }
    }

    private Pane assembleView(Stage primaryStage) {
        VBox rootPane = new VBox();
        rootPane.setSpacing(10);
        rootPane.setPadding(new Insets(10));
        rootPane.setStyle("-fx-base: #84a7ad;");
        rootPane.getChildren().add(new Label("GUI goes here."));

        primaryStage.setScene(new Scene(rootPane));
        primaryStage.setResizable(false);
        primaryStage.show();

        return rootPane;
    }
}

EntryDao类:

代码语言:javascript
运行
复制
package tmp.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

/**
 * DB creation and schema:
 * CREATE DATABASE db_name;
 * CREATE USER db_username;
 * <p>
 * USE db_name;
 * GRANT ALL ON db_name.* TO db_username;
 * <p>
 * SET PASSWORD FOR spz = PASSWORD('username123');
 * FLUSH PRIVILEGES;
 * <p>
 * CREATE TABLE Entry (
 * entry_ID INT NOT NULL AUTO_INCREMENT,
 * name   TEXT NOT NULL,
 * <p>
 * PRIMARY KEY (entry_ID)
 * );
 */
@Component
public class EntryDao {
    /**
     * application.properties:
     * spring.datasource.driver-class-name = com.mysql.jdbc.Driver
     * spring.datasource.url = jdbc:mysql://localhost:3306/db_name?useSSL=false&serverTimezone=UTC
     * spring.datasource.username = db_username
     * spring.datasource.password = username123
     */
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void db_transaction_test() {
        for (int i = 0; i < 10; i++) {
            try {
                int entry_name = getEntryId("entry_" + i);
                System.out.println("Created entry id=" + entry_name);
            } catch (EntryDao.DaoException e) {
                e.printStackTrace();
            }

            if (i == 5) {
                throw new RuntimeException("Testing data upload procedure break.");
            }
        }
    }

    public int getEntryId(String entryName) throws DaoException {
        List<DbEntry> dbEntries = retrieveEntriesFor(entryName);

        if (dbEntries.size() == 1) {
            return dbEntries.get(0).getEntry_ID();
        } else if (dbEntries.size() == 0) {
            String sqlInsert = "INSERT INTO Entry (name) VALUES (?)";
            jdbcTemplate.update(sqlInsert, entryName);
            dbEntries = retrieveEntriesFor(entryName);
            if (dbEntries.size() == 1) {
                return dbEntries.get(0).getEntry_ID();
            } else {
                throw new DaoException("Invalid results amount received after creating new (" + dbEntries.size() + ") when getting entry for name: " + entryName);
            }
        } else {
            throw new DaoException("Invalid results amount received (" + dbEntries.size() + ") when getting entry for name: " + entryName);
        }
    }

    private List<DbEntry> retrieveEntriesFor(String entryName) {
        return jdbcTemplate.query("SELECT * FROM Entry WHERE name=?;", (ResultSet result, int rowNum) -> unMarshal(result), entryName);
    }

    private DbEntry unMarshal(ResultSet result) throws SQLException {
        DbEntry dbEntry = new DbEntry();
        dbEntry.setEntry_ID(result.getInt("entry_ID"));
        dbEntry.setName(result.getString("name"));
        return dbEntry;
    }

    public class DbEntry {
        private int entry_ID;
        private String name;

        int getEntry_ID() { return entry_ID; }
        void setEntry_ID(int entry_ID) { this.entry_ID = entry_ID; }
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
    }

    public class DaoException extends Throwable { DaoException(String err_msg) { super(err_msg); } }
}

STACKTRACE

代码语言:javascript
运行
复制
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.4.3.RELEASE)

2017-01-10 09:41:48.902  INFO 1860 --- [JavaFX-Launcher] o.s.boot.SpringApplication               : Starting application on alwihasolaptop with PID 1860 (started by alwi in C:\alwi\Workspace_SPZ\GCodeClient)
2017-01-10 09:41:48.905  INFO 1860 --- [JavaFX-Launcher] o.s.boot.SpringApplication               : No active profile set, falling back to default profiles: default
2017-01-10 09:41:48.965  INFO 1860 --- [JavaFX-Launcher] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@18660f3: startup date [Tue Jan 10 09:41:48 CET 2017]; root of context hierarchy
2017-01-10 09:41:49.917  INFO 1860 --- [JavaFX-Launcher] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-01-10 09:41:49.927  INFO 1860 --- [JavaFX-Launcher] o.s.boot.SpringApplication               : Started application in 1.384 seconds (JVM running for 1.969)
Created entry id=73
Created entry id=74
Created entry id=75
Created entry id=76
Created entry id=77
Created entry id=78
java.lang.RuntimeException: Testing data upload procedure break.
    at tmp.dao.EntryDao.db_transaction_test(EntryDao.java:53)
    at tmp.dao.EntryDao$$FastClassBySpringCGLIB$$a857b433.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:721)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656)
    at tmp.dao.EntryDao$$EnhancerBySpringCGLIB$$84e8651e.db_transaction_test(<generated>)
    at tmp.SpringTransactional.start(SpringTransactional.java:45)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)
Created entry id=73
Created entry id=74
Created entry id=75
Created entry id=76
Created entry id=77
Created entry id=78
2017-01-10 09:41:50.545  INFO 1860 --- [lication Thread] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@18660f3: startup date [Tue Jan 10 09:41:48 CET 2017]; root of context hierarchy
java.lang.RuntimeException: Testing data upload procedure break.
    at tmp.SpringTransactional.db_transaction_test(SpringTransactional.java:71)
    at tmp.SpringTransactional.start(SpringTransactional.java:52)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)
2017-01-10 09:41:50.546  INFO 1860 --- [lication Thread] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

Process finished with exit code 0

解决办法:

到目前为止,我发现的最佳解决方案是使用SpringTransactionTemplate和其他回调类:

代码语言:javascript
运行
复制
package tmp.dao;

public abstract class DbTransactionTask { public abstract void executeTask(); }

SpringTransactionaldb_transaction_test()方法中(请注意,@Transactional是out):

代码语言:javascript
运行
复制
private void db_transaction_test() {
    DbTransactionTask dbTask = new DbTransactionTask() {
        @Override
        public void executeTask() {
            for (int i = 0; i < 10; i++) {
                try {
                    int entry_name = dao.getEntryId("entry_" + i);
                    System.out.println("Created entry id=" + entry_name);
                } catch (EntryDao.DaoException e) {
                    e.printStackTrace();
                }

                if (i == 5) {
                    throw new RuntimeException("Testing data upload procedure break.");
                }
            }
        }
    };

    dao.executeTransactionWithoutResult(dbTask);
}

EntryDao类需要以下附加代码:

代码语言:javascript
运行
复制
@Autowired
private TransactionTemplate transactionTemplate;

public void executeTransactionWithoutResult(DbTransactionTask dbTask) {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
            dbTask.executeTask();
        }
    });
}
票数 0
EN

Stack Overflow用户

发布于 2017-01-09 14:51:33

Spring中的事务的工作方式与AOP在Spring中的工作方式相同:当您从Spring请求一个bean (该bean的方法标记为事务性)时,您实际上收到了该bean的代理,该bean的事务性方法的实现“装饰”了您在实现类中提供的实现。简而言之,代理类中方法的实现开始一个事务,然后调用在实现类中定义的方法,然后提交或回滚事务。

因此,我认为问题在于,SpringTransactional实例不是由Spring应用程序上下文创建的,而是由JavaFX启动过程创建的(即,在调用Application.launch()时由JavaFX框架创建)。因此,Spring不能创建实现事务行为的代理对象。

尝试将数据库功能分解到一个单独的由spring管理的类中,并将其实例注入应用程序类。也就是做一些类似的事情

代码语言:javascript
运行
复制
// Note: I'm only familiar with "traditional" Spring, not Spring boot. 
// Not sure if this annotation is picked up by Spring boot, you may need to 
// make some changes to the config or something to get this working.
@Component
public class DAO {

    @Autowired
    private JdbcTemplate jdbcTemplate ;

    @Transactional
    private void db_transaction_test() {
        // ...
    }

    // ...
}

然后在您的应用程序类中:

代码语言:javascript
运行
复制
@SpringBootApplication
public class SpringTransactional extends Application {
    private Pane viewPane;

    private ConfigurableApplicationContext springContext;

    @Autowired
    private DAO dao ;

    // ...

     @Override
    public void start(Stage primaryStage) {
        viewPane = assembleView(primaryStage);

        try {
            dao.db_transaction_test();
        } catch (RuntimeException e) {
            e.printStackTrace();
        }

        Platform.exit();
    }  

    // ...
}
票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41550272

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档