我想在使用Spring的DB访问中使用JavaFX。然而,我对Spring完全陌生,而且我似乎无法完全理解它的特性,特别是处理.
我将下列依赖项添加到我的项目中:
compile 'org.springframework.boot:spring-boot-starter-jdbc'
runtime 'mysql:mysql-connector-java'
..。在GUI应用程序对DB进行操作时,我希望使用Spring事务处理机制。据我所知,以下代码应该:
for loop
中在DB中创建5个条目RuntimeException
)因此,总结一下:当RuntimeException
被抛出并注释为@Transactional
的方法中时,应该在应用程序退出之前恢复该方法已经创建的所有条目,不是吗?
然而,所有创建的条目都永久保留在DB中(我可以在应用程序退出后看到它们)。因此,首先-我是否正确地理解了这些事务应该如何工作?如果是这样的话,那么如何使它们像我所期望的那样工作呢?
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); }
}
}
发布于 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
对象?
主修班:
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类:
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
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: 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
和其他回调类:
package tmp.dao;
public abstract class DbTransactionTask { public abstract void executeTask(); }
在SpringTransactional
类db_transaction_test()
方法中(请注意,@Transactional
是out):
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
类需要以下附加代码:
@Autowired
private TransactionTemplate transactionTemplate;
public void executeTransactionWithoutResult(DbTransactionTask dbTask) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
dbTask.executeTask();
}
});
}
发布于 2017-01-09 14:51:33
Spring中的事务的工作方式与AOP在Spring中的工作方式相同:当您从Spring请求一个bean (该bean的方法标记为事务性)时,您实际上收到了该bean的代理,该bean的事务性方法的实现“装饰”了您在实现类中提供的实现。简而言之,代理类中方法的实现开始一个事务,然后调用在实现类中定义的方法,然后提交或回滚事务。
因此,我认为问题在于,SpringTransactional
实例不是由Spring应用程序上下文创建的,而是由JavaFX启动过程创建的(即,在调用Application.launch()
时由JavaFX框架创建)。因此,Spring不能创建实现事务行为的代理对象。
尝试将数据库功能分解到一个单独的由spring管理的类中,并将其实例注入应用程序类。也就是做一些类似的事情
// 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() {
// ...
}
// ...
}
然后在您的应用程序类中:
@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();
}
// ...
}
https://stackoverflow.com/questions/41550272
复制相似问题