Skip to content

Commit 229542e

Browse files
committed
[#2651] Add test for Lock Timeout
1 parent de0eb22 commit 229542e

File tree

1 file changed

+205
-0
lines changed

1 file changed

+205
-0
lines changed
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/* Hibernate, Relational Persistence for Idiomatic Java
2+
*
3+
* SPDX-License-Identifier: Apache-2.0
4+
* Copyright: Red Hat Inc. and Hibernate Authors
5+
*/
6+
package org.hibernate.reactive;
7+
8+
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
9+
import org.hibernate.cfg.AvailableSettings;
10+
import org.hibernate.cfg.Configuration;
11+
import org.hibernate.reactive.testing.SqlStatementTracker;
12+
13+
import org.junit.jupiter.api.BeforeEach;
14+
import org.junit.jupiter.api.Test;
15+
16+
import io.vertx.junit5.VertxTestContext;
17+
import jakarta.persistence.Entity;
18+
import jakarta.persistence.FetchType;
19+
import jakarta.persistence.Id;
20+
import jakarta.persistence.LockModeType;
21+
import jakarta.persistence.ManyToOne;
22+
import jakarta.persistence.OneToMany;
23+
import java.util.ArrayList;
24+
import java.util.Collection;
25+
import java.util.List;
26+
import java.util.Locale;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType;
30+
31+
public class LockTimeoutTest extends BaseReactiveTest {
32+
private static final Long CHILD_ID = 1L;
33+
34+
private static SqlStatementTracker sqlTracker;
35+
36+
@Override
37+
protected Configuration constructConfiguration() {
38+
Configuration configuration = super.constructConfiguration();
39+
configuration.setProperty( AvailableSettings.JAKARTA_LOCK_TIMEOUT, 1000 );
40+
// Construct a tracker that collects query statements via the SqlStatementLogger framework.
41+
// Pass in configuration properties to hand off any actual logging properties
42+
sqlTracker = new SqlStatementTracker( LockTimeoutTest::selectQueryFilter, configuration.getProperties() );
43+
return configuration;
44+
}
45+
46+
@BeforeEach
47+
public void clearTracker() {
48+
sqlTracker.clear();
49+
}
50+
51+
@Override
52+
protected void addServices(StandardServiceRegistryBuilder builder) {
53+
sqlTracker.registerService( builder );
54+
}
55+
56+
private static boolean selectQueryFilter(String s) {
57+
return s.toLowerCase().startsWith( "select " )
58+
|| s.toLowerCase( Locale.ROOT ).startsWith( "set" )
59+
|| s.toLowerCase( Locale.ROOT ).startsWith( "show" );
60+
}
61+
62+
@Override
63+
protected Collection<Class<?>> annotatedEntities() {
64+
return List.of( Parent.class, Child.class );
65+
}
66+
67+
68+
@Test
69+
public void testLockTimeOut(VertxTestContext context) {
70+
Parent parent = new Parent( 1L, "Lio" );
71+
Child child = new Child( CHILD_ID, "And" );
72+
test(
73+
context, getMutinySessionFactory()
74+
.withTransaction( session -> session.persistAll( parent, child ) )
75+
.chain( () -> getMutinySessionFactory()
76+
.withTransaction(
77+
session ->
78+
session.createQuery( "from Child c", Child.class )
79+
.setLockMode( LockModeType.PESSIMISTIC_WRITE )
80+
.getSingleResult().invoke( c -> {
81+
assertThat( c ).isNotNull();
82+
assertThat( c.getId() ).isEqualTo( CHILD_ID );
83+
assertTimeoutApplied();
84+
} )
85+
)
86+
)
87+
);
88+
}
89+
90+
/**
91+
* @return true if the query contains the expected the expected timeout statements
92+
*/
93+
private void assertTimeoutApplied() {
94+
List<String> loggedQueries = sqlTracker.getLoggedQueries();
95+
switch ( dbType() ) {
96+
case POSTGRESQL -> {
97+
assertThat( loggedQueries ).hasSize( 4 );
98+
assertThat( loggedQueries.get( 0 ).toLowerCase( Locale.ROOT ) ).isEqualTo(
99+
"select current_setting('lock_timeout', true)" );
100+
assertThat( loggedQueries.get( 1 ).toLowerCase( Locale.ROOT ) ).isEqualTo( "set lock_timeout = 1000" );
101+
assertThat( loggedQueries.get( 3 ).toLowerCase( Locale.ROOT ) ).isEqualTo( "set lock_timeout = 0" );
102+
}
103+
case COCKROACHDB -> {
104+
assertThat( loggedQueries ).hasSize( 4 );
105+
assertThat( loggedQueries.get( 0 ).toLowerCase( Locale.ROOT ) ).isEqualTo( "show lock_timeout" );
106+
assertThat( loggedQueries.get( 1 ).toLowerCase( Locale.ROOT ) ).isEqualTo( "set lock_timeout = 1000" );
107+
assertThat( loggedQueries.get( 3 ).toLowerCase( Locale.ROOT ) ).isEqualTo( "set lock_timeout = 0" );
108+
}
109+
case SQLSERVER -> {
110+
assertThat( loggedQueries ).hasSize( 4 );
111+
assertThat( loggedQueries.get( 0 ).toLowerCase( Locale.ROOT ) ).isEqualTo( "select @@lock_timeout" );
112+
assertThat( loggedQueries.get( 1 ).toLowerCase( Locale.ROOT ) ).isEqualTo( "set lock_timeout 1000" );
113+
assertThat( loggedQueries.get( 3 ).toLowerCase( Locale.ROOT ) ).isEqualTo( "set lock_timeout -1" );
114+
}
115+
// it seems ORM has not yet enabled connection lock timeout support for MariaDB/MySQL, see MySQLLockingSupport#getLockTimeout(TimeOut)
116+
case MARIA, MYSQL -> assertThat( loggedQueries ).hasSize( 1 );
117+
// {
118+
// assertThat( loggedQueries ).hasSize( 4 );
119+
// assertThat( loggedQueries.get( 0 ).toLowerCase( Locale.ROOT ) ).isEqualTo(
120+
// "SELECT @@SESSION.innodb_lock_wait_timeout" );
121+
// assertThat( loggedQueries.get( 1 ).toLowerCase( Locale.ROOT ) ).isEqualTo(
122+
// "SET @@SESSION.innodb_lock_wait_timeout = 1000" );
123+
// assertThat( loggedQueries.get( 3 ).toLowerCase( Locale.ROOT ) ).isEqualTo(
124+
// "SET @@SESSION.innodb_lock_wait_timeout = 0" );
125+
// Oracle does not support connection lock timeout but only per-query timeouts
126+
// }
127+
case ORACLE -> {
128+
assertThat( loggedQueries ).hasSize( 1 );
129+
assertThat( loggedQueries.get( 0 ).toLowerCase( Locale.ROOT ) ).contains( "for update of c1_0.id wait 1" );
130+
}
131+
// DB2 does not support wait timeouts on locks
132+
case DB2 -> assertThat( loggedQueries ).hasSize( 1 );
133+
default -> throw new IllegalArgumentException( "Database not recognized: " + dbType().name() );
134+
}
135+
;
136+
}
137+
138+
@Entity(name = "Parent")
139+
public static class Parent {
140+
141+
@Id
142+
private Long id;
143+
144+
private String name;
145+
146+
@OneToMany(fetch = FetchType.EAGER)
147+
public List<Child> children = new ArrayList<>();
148+
149+
public Parent() {
150+
}
151+
152+
public Parent(Long id, String name) {
153+
this.id = id;
154+
this.name = name;
155+
}
156+
157+
public void add(Child child) {
158+
children.add( child );
159+
}
160+
161+
public Long getId() {
162+
return id;
163+
}
164+
165+
public String getName() {
166+
return name;
167+
}
168+
169+
public List<Child> getChildren() {
170+
return children;
171+
}
172+
}
173+
174+
@Entity(name = "Child")
175+
public static class Child {
176+
177+
@Id
178+
private Long id;
179+
180+
public String name;
181+
182+
@ManyToOne(fetch = FetchType.LAZY)
183+
public Parent parent;
184+
185+
public Child() {
186+
}
187+
188+
public Child(Long id, String name) {
189+
this.id = id;
190+
this.name = name;
191+
}
192+
193+
public Long getId() {
194+
return id;
195+
}
196+
197+
public String getName() {
198+
return name;
199+
}
200+
201+
public Parent getParent() {
202+
return parent;
203+
}
204+
}
205+
}

0 commit comments

Comments
 (0)