PreparedStatement.execute() 在 Spring 单元测试中挂起

发布于 2024-12-08 13:22:46 字数 4128 浏览 0 评论 0原文

我在 Spring MVC 应用程序中进行单元测试时遇到了一些问题。坦率地说,由于我缺乏从头开始编写测试套件的经验,因此我很可能错误地设计了单元测试。

我目前设计的方式是,例如,为了测试用户服务,测试套件使用原始 SQL 语句来验证数据是否正确插入/检索/更新。我遇到的问题是,执行第一个准备好的语句后,后续语句将挂在 execute() 方法上。测试的结果最终是“超出锁定等待超时;尝试重新启动事务”

根据我在网上阅读的内容,这可能是一个事务管理问题,有人没有释放锁,但是我不知道如何最好地做到这一点,甚至不知道在哪里做到这一点。

下面是一些相关代码,如果需要更多代码,请告诉我。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"/applicationContext-base.xml", "/application-security.xml"})
@TransactionConfiguration(transactionManager="txManager")
@Transactional
public class TestUserService {

    @Autowired
    UsersService userService;

    @Autowired
    DataSource dataSource;

    Connection connection;

    @Before
    public void setup() throws Exception{
        connection = dataSource.getConnection();
    }

    @Test
    public void testCreateUser() throws Exception{

        Collection<GrantedAuthorityImpl> auths = new ArrayList<GrantedAuthorityImpl>();
        auths.add(new GrantedAuthorityImpl(SecurityConstants.ROLE_USER));


        User user = new User("testUser", "testpassword", true, true, true, true, auths, "salt");

        User tmp = userService.createUser(user);


        PreparedStatement ps = connection.prepareStatement("select id, username, password, created, enabled, salt from users where id = ?");
        PreparedStatement ps2 = connection.prepareStatement("select user, authority from user_authorities where user = ?");

        ps.setLong(1, tmp.getId());
        ps2.setLong(1, tmp.getId());

        ResultSet rs = ps.executeQuery();
        ResultSet rs2 = ps2.executeQuery();

        rs.first();
        rs2.first();

        Collection<GrantedAuthorityImpl> authsFromDb = new ArrayList<GrantedAuthorityImpl>();

        rs.first();
        do{
            authsFromDb.add(new GrantedAuthorityImpl(rs2.getString("authority")));
        }while(rs2.next());

        User tmp2 = new User(rs.getString("username"), rs.getString("password"), rs.getBoolean("enabled"), true, true, true, authsFromDb, rs.getString("salt"));

        Assert.assertEquals(tmp.getUsername(), tmp2.getUsername());
        Assert.assertEquals(tmp.getId(), tmp2.getId());
        Assert.assertEquals(tmp.getPassword(), tmp2.getPassword());
        Assert.assertEquals(tmp.getSalt(), tmp2.getSalt());
        Assert.assertEquals(tmp.getAuthorities(), tmp2.getAuthorities());
        Assert.assertEquals(tmp.isEnabled(), tmp2.isEnabled());

    }

    @Test
    public void testSaveUser() throws Exception{
        long createdTime = System.currentTimeMillis();
        String insertionQry = "insert into users (username, password, created, enabled, salt) values ('chris', 'somepassword'," + createdTime + ",1,'salt')";


        PreparedStatement ps = connection.prepareStatement(insertionQry, Statement.RETURN_GENERATED_KEYS);
        ps.execute();
        ResultSet rs = ps.getGeneratedKeys();
        rs.first();
        long id = rs.getLong(1);

        Assert.assertEquals(true, id != 0);

        String loadQry = "select id, username, password, created, enabled, salt from users where id = " + id;

        ps = connection.prepareStatement(loadQry);
        rs = ps.executeQuery();

        rs.first();

        Assert.assertEquals(rs.getString("username"), "chris");
        Assert.assertEquals(rs.getString("password"), "somepassword");
        Assert.assertEquals(rs.getBoolean("enabled"), true);
        Assert.assertEquals(rs.getString("salt"), "salt");


        User user = new User("second_username", "newpassword", false, true, true, true, AuthorityUtils.NO_AUTHORITIES, "secondsalt");
        user.setId(rs.getLong("id"));

        userService.saveUser(user);

        ps = connection.prepareStatement(loadQry);
        rs = ps.executeQuery();

        rs.first();

        Assert.assertEquals(rs.getString("username"), "second_username");
        Assert.assertEquals(rs.getString("password"), "newpassword");
        Assert.assertEquals(rs.getBoolean("enabled"), false);
        Assert.assertEquals(rs.getString("salt"), "secondsalt");



    }

I'm having some trouble with my unit tests in a Spring MVC application. In full disclosure, there's a good chance I'm designing my unit tests incorrectly given my lack of experience writing a test suite from the ground up.

The way I have it designed currently is, for instance, to test a user service, the test suite uses raw SQL statements to verify the data is inserted/retrieved/updated correctly. The problem I'm having is that after the frist prepared statement is executed, subsequent statements hang on the execute() method. The result of the test ends up being "Lock wait timeout exceeded; try restarting transaction"

Based on what I've read online, this is likely a transaction management issue and somebody isn't releasing a lock but I'm not sure how best to do that or even where to do that.

Some relevant code is below, let me know if more code is necessary.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"/applicationContext-base.xml", "/application-security.xml"})
@TransactionConfiguration(transactionManager="txManager")
@Transactional
public class TestUserService {

    @Autowired
    UsersService userService;

    @Autowired
    DataSource dataSource;

    Connection connection;

    @Before
    public void setup() throws Exception{
        connection = dataSource.getConnection();
    }

    @Test
    public void testCreateUser() throws Exception{

        Collection<GrantedAuthorityImpl> auths = new ArrayList<GrantedAuthorityImpl>();
        auths.add(new GrantedAuthorityImpl(SecurityConstants.ROLE_USER));


        User user = new User("testUser", "testpassword", true, true, true, true, auths, "salt");

        User tmp = userService.createUser(user);


        PreparedStatement ps = connection.prepareStatement("select id, username, password, created, enabled, salt from users where id = ?");
        PreparedStatement ps2 = connection.prepareStatement("select user, authority from user_authorities where user = ?");

        ps.setLong(1, tmp.getId());
        ps2.setLong(1, tmp.getId());

        ResultSet rs = ps.executeQuery();
        ResultSet rs2 = ps2.executeQuery();

        rs.first();
        rs2.first();

        Collection<GrantedAuthorityImpl> authsFromDb = new ArrayList<GrantedAuthorityImpl>();

        rs.first();
        do{
            authsFromDb.add(new GrantedAuthorityImpl(rs2.getString("authority")));
        }while(rs2.next());

        User tmp2 = new User(rs.getString("username"), rs.getString("password"), rs.getBoolean("enabled"), true, true, true, authsFromDb, rs.getString("salt"));

        Assert.assertEquals(tmp.getUsername(), tmp2.getUsername());
        Assert.assertEquals(tmp.getId(), tmp2.getId());
        Assert.assertEquals(tmp.getPassword(), tmp2.getPassword());
        Assert.assertEquals(tmp.getSalt(), tmp2.getSalt());
        Assert.assertEquals(tmp.getAuthorities(), tmp2.getAuthorities());
        Assert.assertEquals(tmp.isEnabled(), tmp2.isEnabled());

    }

    @Test
    public void testSaveUser() throws Exception{
        long createdTime = System.currentTimeMillis();
        String insertionQry = "insert into users (username, password, created, enabled, salt) values ('chris', 'somepassword'," + createdTime + ",1,'salt')";


        PreparedStatement ps = connection.prepareStatement(insertionQry, Statement.RETURN_GENERATED_KEYS);
        ps.execute();
        ResultSet rs = ps.getGeneratedKeys();
        rs.first();
        long id = rs.getLong(1);

        Assert.assertEquals(true, id != 0);

        String loadQry = "select id, username, password, created, enabled, salt from users where id = " + id;

        ps = connection.prepareStatement(loadQry);
        rs = ps.executeQuery();

        rs.first();

        Assert.assertEquals(rs.getString("username"), "chris");
        Assert.assertEquals(rs.getString("password"), "somepassword");
        Assert.assertEquals(rs.getBoolean("enabled"), true);
        Assert.assertEquals(rs.getString("salt"), "salt");


        User user = new User("second_username", "newpassword", false, true, true, true, AuthorityUtils.NO_AUTHORITIES, "secondsalt");
        user.setId(rs.getLong("id"));

        userService.saveUser(user);

        ps = connection.prepareStatement(loadQry);
        rs = ps.executeQuery();

        rs.first();

        Assert.assertEquals(rs.getString("username"), "second_username");
        Assert.assertEquals(rs.getString("password"), "newpassword");
        Assert.assertEquals(rs.getBoolean("enabled"), false);
        Assert.assertEquals(rs.getString("salt"), "secondsalt");



    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夕嗳→ 2024-12-15 13:22:46

为了将原始 JDBC Connection 与 Spring 事务管理结合使用,您需要以 DataSourceUtils.getConnection(dataSource) 的形式获取它们,请参阅 DataSourceTransactionManagement。也许这就是原因。

因此,问题是通过 dataSource.getConnection() 获取并在测试代码中使用的 Connection 与代码中使用的 Spring 管理的连接不同已测试。因此,在这些连接中执行的查询属于不同的事务,并且从单个线程在多个事务中执行查询通常会导致死锁。

使用 DataSourceUtils 时,您将获得与正在测试的代码相同的 Spring 管理连接,以便所有查询都在单个事务中执行。

In order to use raw JDBC Connections with Spring transaction management you need to obtain them as DataSourceUtils.getConnection(dataSource), see DataSourceTransactionManagement. Perhaps that's the cause.

So, the problem is that the Connection obtained via dataSource.getConnection() and used in the test code is not the same as a Spring-managed connection used in the code being tested. Thus, queries executed in these connections belong to different transactions, and executing queries in many transactions from a single thread often leads to deadlocks.

When using DataSourceUtils you get the same Spring-managed connection as the code being tested, so that all your queries are executed in a single transaction.

彻夜缠绵 2024-12-15 13:22:46

我发现您的测试有几个问题:

  1. 您的测试同时测试了很多东西。您需要研究的是隔离(也称为模拟)框架,以便您的测试可以更加精细。
  2. 执行数据库测试很棘手(我几乎没有测试这一层的经验)。您最好进行一些抽象,这样在执行测试时就不会实际使用实际资源。如果您发现必须使用真实资源,那么它们应该非常简单,并且有一个干净的数据库可供运行,以避免数据污染您的测试结果。
  3. 永远不要在测试中重复字符串,就像生产代码一样(即“Chris”)。您可能需要在其他地方进行参考。根据测试框架,您可能被允许拥有一个带有共享对象等的基类,您可以根据自己的喜好进行自定义。

I see several problems with your testing:

  1. Your tests are testing many things at once. What you need to look into are isolation (also known as mocking) frameworks so that your tests can be more granular.
  2. Performing database tests are tricky (I have little experience testing this layer). You are probably better off abstracting a little so that you don't actually use real resources when performing tests. If you find that it's a must to use real resources, they should be extremely simple and have a clean database to run against to avoid data from polluting your test results.
  3. Never repeat strings in your tests, just like production code (ie "Chris"). You may need to make a reference elsewhere. Depending on the testing framework, you may be allowed to have a base class with shared objects, etc. that you can customize to your heart's content.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文