Spring,休眠事务。加入在 A 中创建的线程 B 中的事务。可能吗?
Spring, Hibernate transactions. Joining a transaction in Thread B created in A. Possible?
是否可以在另一个线程中使用事务?
比如传递在线程A中创建的事务,然后在线程B[=33=中执行一些逻辑] 在同一笔交易中?
我有两个队列和单独的执行程序来处理某些实体类型的填充。
但是,批处理作业管理两个填充 并等待每个完成。创建两个事务不需要。如果一个失败,理想情况下我希望所有数据都回滚,因此将它们作为一个事务 运行 是理想的,并且它提供了改进的性能。
那么,是否可以创建一个事务,将其传递给另一个线程,在第一个线程的边界内执行一些东西?
我正在使用 Spring 和 Hibernate,目前正在使用
TransactionTemplate template = new TransactionTemplate( getTransactionManager() );
template.setPropagationBehavior(propagationBehavior);
template.setReadOnly(readOnly);
TransactionStatus status = getTransactionManager().getTransaction(template);
创建交易,根本不使用注释,也不打算这样做。
Spring 不可能。所有基于事务的代码最终都在 TransactionSynchronizationManager
中结束,其中充满了 ThreadLocal
并且无法将这些值从一个线程复制到另一个线程。
如果您希望能够做到这一点,您可以使用 Spring 来获取 DataSource
但您必须创建自己的连接并手动创建 Hibernate 会话。 Spring的PlatformTransactionManager
是不可能的。
[编辑] 在你的案例中有多个线程有什么意义?如果你想并行工作,那么正确的方法是让 N 个线程准备应该插入数据库的数据,然后用一个线程创建 1 个事务,然后完成所有工作。
是的,这意味着您需要在内存中保留大量数据。
如果您真的不想这样做,那么下一个解决方案是使用工作表,每个线程都可以在其中放置它们的结果。当两个线程都完成后,您将启动另一个线程来锁定工作表并运行一些 SQL 查询以将数据复制到正确的位置。
永远记住,数据库连接、SQL 和线程不能混用。数据库是全局状态。如果您同时从多个地方更改全局状态,您将始终面临各种奇怪的问题。尽量避免这种情况。将工作分成许多小的、独立的任务(即,当每个任务都有自己的事务时,它们工作得很好)。如果你不能这样做,你需要重新考虑你的设计,直到你可以或不能使用线程。
在 Spring 中,实际上可以使用如下简单的技巧将事务上下文从一个线程传输到另一个线程:
- 必须检索与线程 1 上的事务上下文相关的所有线程局部变量并将其存储在可以从线程 2 的作用域中引用的某个位置。在 Hibernate 的情况下,当前的
org.springframework.orm.hibernate4.SessionHolder
应该足够了,因为它还会将 link 保留到正在进行的事务中。
- 然后,线程 2 可以在其执行的最开始使用以下逻辑启动:将第一步中检索到的所有线程局部变量绑定到线程 2 上的相同变量,使用 Spring的实用程序,如
org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(Object, Object)
。在此之后,第二个线程应该能够与其父线程在同一个 Hibernate 会话和事务中工作。
我们可能需要注意一些细微的细节,但以下代码应该可以完成工作:
final SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
Runnable newTask = new Runnable() {
@Override
public void run() {
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
// Business stuff here.
}
};
Executors.newFixedThreadPool(1).submit(newTask);
希望对您有所帮助。
我决定提交我自己的答案,允许在所有环境和框架中以不同的方式发生这种情况,方法是让其他线程将工作委托给原始线程。
示例:
public static void main(String[] args) throws Exception {
System.out.println(Thread.currentThread().getId() + ": (1)");
JoinLock lock = new JoinLock();
new Thread(() -> {
lock.delegate(() -> {
System.out.println(Thread.currentThread().getId() + ": (a) "); // Will execute in the originating thread
});
}).start();
lock.await();
System.out.println(Thread.currentThread().getId() + ": (2)");
}
输出:
1: (1)
1: (a)
1: (2)
另一个使用传递的可运行对象和更多详细信息的示例:
public static void main(String[] args) throws Exception {
System.out.println(Thread.currentThread().getId() + ": (1)");
new JoinLock().await((lock) -> {
new Thread(() -> {
// Should execute as another thread
System.out.println(Thread.currentThread().getId() + ": (a) ");
// Will execute in the originating thread
lock.delegate(() -> {
System.out.println(Thread.currentThread().getId() + ": (b) ");
sleep(2000);
System.out.println(Thread.currentThread().getId() + ": (c) ");
});
// This should execute first when the delegate has finished executed, so after 1 : (c) but since it's a separate thread,
// ofcourse the originating thread might also execute prior, which happens because of our sleep here
sleep(2000);
System.out.println(Thread.currentThread().getId() + ": (d) ");
}).start();
});
System.out.println(Thread.currentThread().getId() + ": (2)");
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
输出:
1: (1)
13: (a)
1: (b)
1: (c)
1: (2)
13: (d)
使用执行器它会是这样的:
new JoinLock().await((lock) -> {
Executors.newFixedThreadPool(1).submit(() -> {
lock.delegate(runnable);
});
});
示例中使用的锁可以在这里找到:
GitHub: Opensource repository. Have a look at: JoinLock.java which also uses: SimpleLock.java.
是否可以在另一个线程中使用事务?
比如传递在线程A中创建的事务,然后在线程B[=33=中执行一些逻辑] 在同一笔交易中?
我有两个队列和单独的执行程序来处理某些实体类型的填充。
但是,批处理作业管理两个填充 并等待每个完成。创建两个事务不需要。如果一个失败,理想情况下我希望所有数据都回滚,因此将它们作为一个事务 运行 是理想的,并且它提供了改进的性能。
那么,是否可以创建一个事务,将其传递给另一个线程,在第一个线程的边界内执行一些东西?
我正在使用 Spring 和 Hibernate,目前正在使用
TransactionTemplate template = new TransactionTemplate( getTransactionManager() );
template.setPropagationBehavior(propagationBehavior);
template.setReadOnly(readOnly);
TransactionStatus status = getTransactionManager().getTransaction(template);
创建交易,根本不使用注释,也不打算这样做。
Spring 不可能。所有基于事务的代码最终都在 TransactionSynchronizationManager
中结束,其中充满了 ThreadLocal
并且无法将这些值从一个线程复制到另一个线程。
如果您希望能够做到这一点,您可以使用 Spring 来获取 DataSource
但您必须创建自己的连接并手动创建 Hibernate 会话。 Spring的PlatformTransactionManager
是不可能的。
[编辑] 在你的案例中有多个线程有什么意义?如果你想并行工作,那么正确的方法是让 N 个线程准备应该插入数据库的数据,然后用一个线程创建 1 个事务,然后完成所有工作。
是的,这意味着您需要在内存中保留大量数据。
如果您真的不想这样做,那么下一个解决方案是使用工作表,每个线程都可以在其中放置它们的结果。当两个线程都完成后,您将启动另一个线程来锁定工作表并运行一些 SQL 查询以将数据复制到正确的位置。
永远记住,数据库连接、SQL 和线程不能混用。数据库是全局状态。如果您同时从多个地方更改全局状态,您将始终面临各种奇怪的问题。尽量避免这种情况。将工作分成许多小的、独立的任务(即,当每个任务都有自己的事务时,它们工作得很好)。如果你不能这样做,你需要重新考虑你的设计,直到你可以或不能使用线程。
在 Spring 中,实际上可以使用如下简单的技巧将事务上下文从一个线程传输到另一个线程:
- 必须检索与线程 1 上的事务上下文相关的所有线程局部变量并将其存储在可以从线程 2 的作用域中引用的某个位置。在 Hibernate 的情况下,当前的
org.springframework.orm.hibernate4.SessionHolder
应该足够了,因为它还会将 link 保留到正在进行的事务中。 - 然后,线程 2 可以在其执行的最开始使用以下逻辑启动:将第一步中检索到的所有线程局部变量绑定到线程 2 上的相同变量,使用 Spring的实用程序,如
org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(Object, Object)
。在此之后,第二个线程应该能够与其父线程在同一个 Hibernate 会话和事务中工作。
我们可能需要注意一些细微的细节,但以下代码应该可以完成工作:
final SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
Runnable newTask = new Runnable() {
@Override
public void run() {
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
// Business stuff here.
}
};
Executors.newFixedThreadPool(1).submit(newTask);
希望对您有所帮助。
我决定提交我自己的答案,允许在所有环境和框架中以不同的方式发生这种情况,方法是让其他线程将工作委托给原始线程。
示例:
public static void main(String[] args) throws Exception {
System.out.println(Thread.currentThread().getId() + ": (1)");
JoinLock lock = new JoinLock();
new Thread(() -> {
lock.delegate(() -> {
System.out.println(Thread.currentThread().getId() + ": (a) "); // Will execute in the originating thread
});
}).start();
lock.await();
System.out.println(Thread.currentThread().getId() + ": (2)");
}
输出:
1: (1)
1: (a)
1: (2)
另一个使用传递的可运行对象和更多详细信息的示例:
public static void main(String[] args) throws Exception {
System.out.println(Thread.currentThread().getId() + ": (1)");
new JoinLock().await((lock) -> {
new Thread(() -> {
// Should execute as another thread
System.out.println(Thread.currentThread().getId() + ": (a) ");
// Will execute in the originating thread
lock.delegate(() -> {
System.out.println(Thread.currentThread().getId() + ": (b) ");
sleep(2000);
System.out.println(Thread.currentThread().getId() + ": (c) ");
});
// This should execute first when the delegate has finished executed, so after 1 : (c) but since it's a separate thread,
// ofcourse the originating thread might also execute prior, which happens because of our sleep here
sleep(2000);
System.out.println(Thread.currentThread().getId() + ": (d) ");
}).start();
});
System.out.println(Thread.currentThread().getId() + ": (2)");
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
输出:
1: (1)
13: (a)
1: (b)
1: (c)
1: (2)
13: (d)
使用执行器它会是这样的:
new JoinLock().await((lock) -> {
Executors.newFixedThreadPool(1).submit(() -> {
lock.delegate(runnable);
});
});
示例中使用的锁可以在这里找到:
GitHub: Opensource repository. Have a look at: JoinLock.java which also uses: SimpleLock.java.