1

I'm trying to do some optimization in my code and would like to spawn a thread where I do a time consuming operation. During the implementation of that optimization I was running into an issue which was driving me crazy. I simplified the issue and created a test case for that specific issue: (I'm using SpringJUnit4ClassRunner so the transaction is properly started at the beginning of the testCRUD method)

Could someone help me understand why the foundParent is null in the thread ?

private Semaphore sema = new Semaphore(0, false);
private long parentId;

@Test
public void testCRUD() {
    //create
    DBParent parent = null;
    {
        parent = new DBParent();
        parentDao.persist(parent);
        parentId = parent.getId();
        assertTrue(parentId > 0);

        parentDao.flush();
    }

    (new Thread(
        new Runnable() {
            public void run() 
            {
                System.out.println("Start adding childs !");
                DBParent foundParent = parentDao.findById(parentId);
                assertTrue(foundParent != null); //ASSERTION FAILS HERE !!!!

                System.out.println("Releasing semaphore !");
                sema.release();
                System.out.println("End adding childs !");
            }
    })).start();

    try {
        System.out.println("Acquiring semaphore !");
        sema.acquire();
    }
    catch (InterruptedException e) {
        e.printStackTrace();
    }
}

=============================EDITED=================================== As per one comment suggestion, I created a threadManager bean which spawn the thread. Here is the code of the threadManager:

public class ThreadManager {
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void executeTask(String Name, Runnable task) {
        (new Thread(task, Name)).start();
    }
 }

Then in the previous test, instead of staring the thread manually, I just post it in the thread manager like this:

@Autowired private ParentDao parentDao;
@Autowired private ThreadManager threadManager;

private Semaphore sema = new Semaphore(0, false);
private long parentId;

@Test
public void testCRUD() {
    //create
    DBParent parent = null;
    {
        parent = new DBParent();
        parentDao.persist(parent);
        parentId = parent.getId();
        assertTrue(parentId > 0);

        parentDao.flush();
    }

    threadManager.executeTask("BG processing...",
        new Runnable() {
            public void run() 
            {
                System.out.println("Start adding childs !");
                DBParent foundParent = parentDao.findById(parentId);
                assertTrue(foundParent != null); //ASSERTION FAILS HERE !!!!

                System.out.println("Releasing semaphore !");
                sema.release();
                System.out.println("End adding childs !");
            }
    });

    try {
        System.out.println("Acquiring semaphore !");
        sema.acquire();
    }
    catch (InterruptedException e) {
        e.printStackTrace();
    }
}

Unfortunately this doesn't work either !!! :-(

4

2 回答 2

5

事务上下文绑定到线程。因此,衍生线程中的代码不会在与初始线程中的代码相同的事务上下文中运行。因此,由于事务隔离(ACID 中的 I),生成的线程看不到初始线程的事务在数据库中插入的内容。

于 2013-11-02T23:22:59.963 回答
0

可以将 Spring 事务绑定到一个新线程,以在其中运行事务和 Hibernate/JPA 访问。但这必须是与其他线程不同的 TX 和 JPA/HB 会话。

的 Spring 代码OpenSessionInViewFilter,是如何将 Hibernate 会话绑定到 Spring 的 TX 管理的一个合理示例。您可以将其简化为相当少的代码。

看:

  • org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
  • OpenSessionInViewFilter.doFilterInternal() -- 这是它实际绑定它的地方
  • TransactionSynchronizationManager.bindResource()
  • TransactionSynchronizationManager.unbindResource()
  • TransactionSynchronizationManager.getResource()

在一个项目 (IIRC) 中,我将此功能包装到一个“ServerThreadHb”类中,以设置和保存以前的线程绑定构造——使用restore()要在块中调用的方法来finally恢复以前的绑定。

对于您发布的代码示例,在单独的线程上运行工作没有多大意义——因为您同步等待工作完成。但是,我假设您计划删除该约束并扩展该功能。

于 2013-11-03T00:02:28.733 回答